diff options
Diffstat (limited to 'core/java')
80 files changed, 2863 insertions, 1464 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index eafb048..c98cf1b 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2232,8 +2232,6 @@ public class Activity extends ContextThemeWrapper /** * Programmatically closes the most recently opened context menu, if showing. - * - * @hide pending API council */ public void closeContextMenu() { mWindow.closePanel(Window.FEATURE_CONTEXT_MENU); diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 2ce2db9..f1c604c 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -16,14 +16,11 @@ package android.app; -import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.content.res.Resources.NotFoundException; @@ -38,6 +35,7 @@ import android.os.ServiceManager; import android.os.SystemClock; import android.server.search.SearchableInfo; import android.text.Editable; +import android.text.InputType; import android.text.TextUtils; import android.text.TextWatcher; import android.util.Log; @@ -45,21 +43,13 @@ import android.view.Gravity; import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; -import android.view.ViewTreeObserver; import android.view.Window; import android.view.WindowManager; -import android.view.View.OnFocusChangeListener; -import android.view.animation.Animation; -import android.view.animation.AnimationUtils; -import android.view.inputmethod.InputMethodManager; -import android.view.inputmethod.EditorInfo; import android.widget.AdapterView; +import android.widget.AutoCompleteTextView; +import android.widget.Button; import android.widget.CursorAdapter; -import android.widget.EditText; -import android.widget.ImageButton; import android.widget.ImageView; -import android.widget.LinearLayout; -import android.widget.ListAdapter; import android.widget.ListView; import android.widget.SimpleCursorAdapter; import android.widget.TextView; @@ -67,8 +57,7 @@ import android.widget.WrapperListAdapter; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemSelectedListener; -import java.util.List; -import java.util.concurrent.LinkedBlockingQueue; +import java.lang.ref.WeakReference; import java.util.concurrent.atomic.AtomicLong; /** @@ -77,7 +66,7 @@ import java.util.concurrent.atomic.AtomicLong; * * @hide */ -public class SearchDialog extends Dialog { +public class SearchDialog extends Dialog implements OnItemClickListener, OnItemSelectedListener { // Debugging support final static String LOG_TAG = "SearchDialog"; @@ -87,7 +76,6 @@ public class SearchDialog extends Dialog { // interaction with runtime IntentFilter mCloseDialogsFilter; IntentFilter mPackageFilter; - private final Handler mHandler = new Handler(); // why isn't Dialog.mHandler shared? private static final String INSTANCE_KEY_COMPONENT = "comp"; private static final String INSTANCE_KEY_APPDATA = "data"; @@ -102,15 +90,10 @@ public class SearchDialog extends Dialog { private static final int INSTANCE_SELECTED_QUERY = -1; // views & widgets - private View mSearchBarLayout; private TextView mBadgeLabel; - private LinearLayout mSearchEditLayout; - private EditText mSearchTextField; - private ImageButton mGoButton; - private ListView mSuggestionsList; + private AutoCompleteTextView mSearchTextField; + private Button mGoButton; - private ViewTreeObserver mViewTreeObserver = null; - // interaction with searchable application private ComponentName mLaunchComponent; private Bundle mAppSearchData; @@ -121,22 +104,20 @@ public class SearchDialog extends Dialog { private SearchableInfo mSearchable; // support for suggestions - private SuggestionsRunner mSuggestionsRunner; private String mUserQuery = null; private int mUserQuerySelStart; private int mUserQuerySelEnd; - private boolean mNonUserQuery = false; private boolean mLeaveJammedQueryOnRefocus = false; private String mPreviousSuggestionQuery = null; - private Context mProviderContext; - private Animation mSuggestionsEntry; - private Animation mSuggestionsExit; - private boolean mSkipNextAnimate; private int mPresetSelection = -1; private String mSuggestionAction = null; private Uri mSuggestionData = null; private String mSuggestionQuery = null; + // support for AutoCompleteTextView suggestions display + private SuggestionsAdapter mSuggestionsAdapter; + + /** * Constructor - fires it up and makes it look like the search UI. * @@ -167,38 +148,25 @@ public class SearchDialog extends Dialog { theWindow.setAttributes(lp); // get the view elements for local access - mSearchBarLayout = findViewById(com.android.internal.R.id.search_bar); mBadgeLabel = (TextView) findViewById(com.android.internal.R.id.search_badge); - mSearchEditLayout = (LinearLayout)findViewById(com.android.internal.R.id.search_edit_frame); - mSearchTextField = (EditText) findViewById(com.android.internal.R.id.search_src_text); - mGoButton = (ImageButton) findViewById(com.android.internal.R.id.search_go_btn); - mSuggestionsList = (ListView) findViewById(com.android.internal.R.id.search_suggest_list); + mSearchTextField = (AutoCompleteTextView) + findViewById(com.android.internal.R.id.search_src_text); + mGoButton = (Button) findViewById(com.android.internal.R.id.search_go_btn); // attach listeners mSearchTextField.addTextChangedListener(mTextWatcher); mSearchTextField.setOnKeyListener(mTextKeyListener); mGoButton.setOnClickListener(mGoButtonClickListener); mGoButton.setOnKeyListener(mButtonsKeyListener); - mSuggestionsList.setOnItemClickListener(mSuggestionsListItemClickListener); - mSuggestionsList.setOnKeyListener(mSuggestionsKeyListener); - mSuggestionsList.setOnFocusChangeListener(mSuggestFocusListener); - mSuggestionsList.setOnItemSelectedListener(mSuggestSelectedListener); // pre-hide all the extraneous elements mBadgeLabel.setVisibility(View.GONE); - mSuggestionsList.setVisibility(View.GONE); // Additional adjustments to make Dialog work for Search // Touching outside of the search dialog will dismiss it setCanceledOnTouchOutside(true); - // Preload animations - mSuggestionsEntry = AnimationUtils.loadAnimation(getContext(), - com.android.internal.R.anim.grow_fade_in); - mSuggestionsExit = AnimationUtils.loadAnimation(getContext(), - com.android.internal.R.anim.fade_out); - // Set up broadcast filters mCloseDialogsFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); @@ -238,18 +206,10 @@ public class SearchDialog extends Dialog { } // OK, we're going to show ourselves - if (mSuggestionsList != null) { - mSuggestionsList.setVisibility(View.GONE); // prevent any flicker if was visible - } - super.show(); setupSearchableInfo(); - // start the suggestions thread (which will mainly idle) - mSuggestionsRunner = new SuggestionsRunner(); - new Thread(mSuggestionsRunner, "SearchSuggestions").start(); - mLaunchComponent = componentName; mAppSearchData = appSearchData; mGlobalSearchMode = globalSearch; @@ -258,26 +218,21 @@ public class SearchDialog extends Dialog { getContext().registerReceiver(mBroadcastReceiver, mCloseDialogsFilter); getContext().registerReceiver(mBroadcastReceiver, mPackageFilter); - mViewTreeObserver = mSearchBarLayout.getViewTreeObserver(); - mViewTreeObserver.addOnGlobalLayoutListener(mGlobalLayoutListener); + // configure the autocomplete aspects of the input box + mSearchTextField.setOnItemClickListener(this); + mSearchTextField.setOnItemSelectedListener(this); + + // attach the suggestions adapter + mSuggestionsAdapter = new SuggestionsAdapter(getContext(), mSearchable); + mSearchTextField.setAdapter(mSuggestionsAdapter); // finally, load the user's initial text (which may trigger suggestions) - mNonUserQuery = false; + mSuggestionsAdapter.setNonUserQuery(false); if (initialQuery == null) { initialQuery = ""; // This forces the preload to happen, triggering suggestions } mSearchTextField.setText(initialQuery); - // If it is not for global search, that means the search dialog is - // launched to input a web address. - if (!globalSearch) { - mSearchTextField.setRawInputType(EditorInfo.TYPE_CLASS_TEXT - | EditorInfo.TYPE_TEXT_VARIATION_URI); - } else { - mSearchTextField.setRawInputType(EditorInfo.TYPE_CLASS_TEXT - | EditorInfo.TYPE_TEXT_VARIATION_NORMAL); - } - if (selectInitialQuery) { mSearchTextField.selectAll(); } else { @@ -315,21 +270,7 @@ public class SearchDialog extends Dialog { // This is OK - it just means we didn't have any registered } - // ignore layout notifications - try { - if (mViewTreeObserver != null) { - mViewTreeObserver.removeGlobalOnLayoutListener(mGlobalLayoutListener); - } - } catch (RuntimeException e) { - // This is OK - none registered or observer "dead" - } - mViewTreeObserver = null; - // dump extra memory we're hanging on to - if (mSuggestionsRunner != null) { - mSuggestionsRunner.cancelSuggestions(); - mSuggestionsRunner = null; - } mLaunchComponent = null; mAppSearchData = null; mSearchable = null; @@ -337,7 +278,6 @@ public class SearchDialog extends Dialog { mSuggestionData = null; mSuggestionQuery = null; mActivityContext = null; - mProviderContext = null; mPreviousSuggestionQuery = null; mUserQuery = null; } @@ -366,9 +306,8 @@ public class SearchDialog extends Dialog { int selectedElement = INSTANCE_SELECTED_QUERY; if (mGoButton.isFocused()) { selectedElement = INSTANCE_SELECTED_BUTTON; - } else if ((mSuggestionsList.getVisibility() == View.VISIBLE) && - mSuggestionsList.isFocused()) { - selectedElement = mSuggestionsList.getSelectedItemPosition(); // 0..n + } else if (mSearchTextField.isPopupShowing()) { + selectedElement = 0; // TODO mSearchTextField.getListSelection() // 0..n } bundle.putInt(INSTANCE_KEY_SELECTED_ELEMENT, selectedElement); @@ -403,11 +342,13 @@ public class SearchDialog extends Dialog { // for some reason, we couldn't re-instantiate return; } - mSkipNextAnimate = true; - mNonUserQuery = true; + mSuggestionsAdapter.setNonUserQuery(true); mSearchTextField.setText(displayQuery); - mNonUserQuery = false; + // TODO because the new query is (not) processed in another thread, we can't just + // take away this flag (yet). The better solution here is going to require a new API + // in AutoCompleteTextView which allows us to change the text w/o changing the suggestions. +// mSuggestionsAdapter.setNonUserQuery(false); // clean up the selection state switch (selectedElement) { @@ -425,6 +366,7 @@ public class SearchDialog extends Dialog { default: // defer selecting a list element until suggestion list appears mPresetSelection = selectedElement; + // TODO mSearchTextField.setListSelection(selectedElement) break; } } @@ -436,6 +378,7 @@ public class SearchDialog extends Dialog { public void onConfigurationChanged(Configuration newConfig) { if (isShowing()) { // Redraw (resources may have changed) + updateSearchButton(); updateSearchBadge(); updateQueryHint(); } @@ -448,10 +391,28 @@ public class SearchDialog extends Dialog { private void setupSearchableInfo() { if (mSearchable != null) { mActivityContext = mSearchable.getActivityContext(getContext()); - mProviderContext = mSearchable.getProviderContext(getContext(), mActivityContext); + updateSearchButton(); updateSearchBadge(); updateQueryHint(); + + // In order to properly configure the input method (if one is being used), we + // need to let it know if we'll be providing suggestions. Although it would be + // difficult/expensive to know if every last detail has been configured properly, we + // can at least see if a suggestions provider has been configured, and use that + // as our trigger. + int inputType = mSearchable.getInputType(); + // We only touch this if the input type is set up for text (which it almost certainly + // should be, in the case of search!) + if ((inputType & InputType.TYPE_MASK_CLASS) == InputType.TYPE_CLASS_TEXT) { + // The existence of a suggestions authority is the proxy for "suggestions + // are available here" + inputType &= ~InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE; + if (mSearchable.getSuggestAuthority() != null) { + inputType |= InputType.TYPE_TEXT_FLAG_AUTO_COMPLETE; + } + } + mSearchTextField.setInputType(inputType); } } @@ -468,6 +429,24 @@ public class SearchDialog extends Dialog { cancel(); } + /** + * Update the text in the search button. Note: This is deprecated functionality, for + * 1.0 compatibility only. + */ + private void updateSearchButton() { + String textLabel = null; + Drawable iconLabel = null; + int textId = mSearchable.getSearchButtonText(); + if (textId != 0) { + textLabel = mActivityContext.getResources().getString(textId); + } else { + iconLabel = getContext().getResources(). + getDrawable(com.android.internal.R.drawable.ic_btn_search); + } + mGoButton.setText(textLabel); + mGoButton.setCompoundDrawablesWithIntrinsicBounds(iconLabel, null, null, null); + } + /** * Setup the search "Badge" if request by mode flags. */ @@ -559,8 +538,8 @@ public class SearchDialog extends Dialog { } updateWidgetState(); // Only do suggestions if actually typed by user - if (!mNonUserQuery) { - updateSuggestions(); + if (mSuggestionsAdapter.getNonUserQuery()) { + mPreviousSuggestionQuery = s.toString(); mUserQuery = mSearchTextField.getText().toString(); mUserQuerySelStart = mSearchTextField.getSelectionStart(); mUserQuerySelEnd = mSearchTextField.getSelectionEnd(); @@ -582,156 +561,6 @@ public class SearchDialog extends Dialog { mGoButton.setFocusable(enabled); } - /** - * In response to a change in the query text, update the suggestions - */ - private void updateSuggestions() { - final String queryText = mSearchTextField.getText().toString(); - mPreviousSuggestionQuery = queryText; - if (DBG_LOG_TIMING == 1) { - dbgLogTiming("updateSuggestions()"); - } - - mSuggestionsRunner.requestSuggestions(mSearchable, queryText); - - // For debugging purposes, put in a lot of strings (really fast typist) - if (DBG_JAM_THREADING > 0) { - for (int ii = 1; ii < DBG_JAM_THREADING; ++ii) { - final String jamQuery = queryText + ii; - mSuggestionsRunner.requestSuggestions(mSearchable, jamQuery); - } - // one final (correct) string for cleanup - mSuggestionsRunner.requestSuggestions(mSearchable, queryText); - } - } - - /** - * This class defines a queued message structure for processing user keystrokes, and a - * thread that allows the suggestions to be gathered out-of-band, and allows us to skip - * over multiple keystrokes if the typist is faster than the content provider. - */ - private class SuggestionsRunner implements Runnable { - - private class Request { - final SearchableInfo mSearchableInfo; // query will set these - final String mQueryText; - final boolean cancelRequest; // cancellation will set this - - // simple constructors - Request(final SearchableInfo searchable, final String queryText) { - mSearchableInfo = searchable; - mQueryText = queryText; - cancelRequest = false; - } - - Request() { - mSearchableInfo = null; - mQueryText = null; - cancelRequest = true; - } - } - - private final LinkedBlockingQueue<Request> mSuggestionsQueue = - new LinkedBlockingQueue<Request>(); - - /** - * Queue up a suggestions request (non-blocking - can safely call from UI thread) - */ - public void requestSuggestions(final SearchableInfo searchable, final String queryText) { - Request request = new Request(searchable, queryText); - try { - mSuggestionsQueue.put(request); - } catch (InterruptedException e) { - // discard the request. - } - } - - /** - * Cancel blocking suggestions, discard any results, and shut down the thread. - * (non-blocking - can safely call from UI thread) - */ - private void cancelSuggestions() { - Request request = new Request(); - try { - mSuggestionsQueue.put(request); - } catch (InterruptedException e) { - // discard the request. - // TODO can we do better here? - } - } - - /** - * This runnable implements the logic for decoupling keystrokes from suggestions. - * The logic isn't quite obvious here, so I'll try to describe it. - * - * Normally we simply sleep waiting for a keystroke. When a keystroke arrives, - * we immediately dispatch a request to gather suggestions. - * - * But this can take a while, so by the time it comes back, more keystrokes may have - * arrived. If anything happened while we were gathering the suggestion, we discard its - * results, and then use the most recent keystroke to start the next suggestions request. - * - * Any request containing cancelRequest == true will cause the thread to immediately - * terminate. - */ - public void run() { - // outer blocking loop simply waits for a suggestion - while (true) { - try { - Request request = mSuggestionsQueue.take(); - if (request.cancelRequest) { - return; - } - - // since we were idle, what we're really interested is the final element - // in the queue. So keep pulling until we get the last element. - // TODO Could we just do some sort of takeHead() here? - while (! mSuggestionsQueue.isEmpty()) { - request = mSuggestionsQueue.take(); - if (request.cancelRequest) { - return; - } - } - final Request useRequest = request; - - // now process the final element (unless it's a cancel - that can be discarded) - - if (useRequest.mSearchableInfo != null) { - - // go get the cursor. this is what takes time. - final Cursor c = getSuggestions(useRequest.mSearchableInfo, - useRequest.mQueryText); - - // We now have a suggestions result. But, if any new requests have arrived, - // we're going to discard them - we don't want to waste time displaying - // out-of-date results, we just want to get going on the next set. - // Note, null cursor is a valid result (no suggestions). This logic also - // supports the need to discard the results *and* stop the thread if a kill - // request arrives during a query. - if (mSuggestionsQueue.size() > 0) { - if (c != null) { - c.close(); - } - } else { - mHandler.post(new Runnable() { - public void run() { - updateSuggestionsWithCursor(c, useRequest.mSearchableInfo); - } - }); - } - } - } catch (InterruptedException e) { - // loop back for more - } - // At this point the queue may contain zero-to-many new requests; We simply - // loop back to handle them (or, block until new requests arrive) - } - } - } - - /** - * Back in the UI thread, handle incoming cursors - */ private final static String[] ONE_LINE_FROM = {SearchManager.SUGGEST_COLUMN_TEXT_1 }; private final static String[] ONE_LINE_ICONS_FROM = {SearchManager.SUGGEST_COLUMN_TEXT_1, SearchManager.SUGGEST_COLUMN_ICON_1, @@ -755,209 +584,6 @@ public class SearchDialog extends Dialog { com.android.internal.R.id.icon2}; /** - * A new cursor (with suggestions) is ready for use. Update the UI. - */ - void updateSuggestionsWithCursor(Cursor c, final SearchableInfo searchable) { - ListAdapter adapter = null; - - // first, check for various conditions that disqualify this cursor - if ((c == null) || (c.getCount() == 0)) { - // no cursor, or cursor with no data - } else if ((searchable != mSearchable) || !isShowing()) { - // race condition (suggestions arrived after conditions changed) - } else { - // check cursor before trying to create list views from it - int colId = c.getColumnIndex("_id"); - int col1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1); - int col2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2); - int colIc1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1); - int colIc2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2); - - boolean minimal = (colId >= 0) && (col1 >= 0); - boolean hasIcons = (colIc1 >= 0) && (colIc2 >= 0); - boolean has2Lines = col2 >= 0; - - if (minimal) { - int layout; - String[] from; - int[] to; - - if (hasIcons) { - if (has2Lines) { - layout = com.android.internal.R.layout.search_dropdown_item_icons_2line; - from = TWO_LINE_ICONS_FROM; - to = TWO_LINE_ICONS_TO; - } else { - layout = com.android.internal.R.layout.search_dropdown_item_icons_1line; - from = ONE_LINE_ICONS_FROM; - to = ONE_LINE_ICONS_TO; - } - } else { - if (has2Lines) { - layout = com.android.internal.R.layout.search_dropdown_item_2line; - from = TWO_LINE_FROM; - to = TWO_LINE_TO; - } else { - layout = com.android.internal.R.layout.search_dropdown_item_1line; - from = ONE_LINE_FROM; - to = ONE_LINE_TO; - } - } - try { - if (DBG_LOG_TIMING == 1) { - dbgLogTiming("updateSuggestions(3)"); - } - adapter = new SuggestionsCursorAdapter(getContext(), layout, c, from, to, - mProviderContext); - if (DBG_LOG_TIMING == 1) { - dbgLogTiming("updateSuggestions(4)"); - } - } catch (RuntimeException e) { - Log.e(LOG_TAG, "Exception while creating SuggestionsCursorAdapter", e); - } - } - - // Provide some help for developers instead of just silently discarding - if ((colIc1 >= 0) != (colIc2 >= 0)) { - Log.w(LOG_TAG, "Suggestion icon column(s) discarded, must be 0 or 2 columns."); - } else if (adapter == null) { - Log.w(LOG_TAG, "Suggestions cursor discarded due to missing required columns."); - } - } - - // if we have a cursor but we're not using it (e.g. disqualified), close it now - if ((c != null) && (adapter == null)) { - c.close(); - c = null; - } - - // we only made an adapter if there were 1+ suggestions. Now, based on the existence - // of the adapter, we'll also show/hide the list. - discardListCursor(mSuggestionsList); - if (adapter == null) { - showSuggestions(false, !mSkipNextAnimate); - } else { - layoutSuggestionsList(); - showSuggestions(true, !mSkipNextAnimate); - } - mSkipNextAnimate = false; - if (DBG_LOG_TIMING == 1) { - dbgLogTiming("updateSuggestions(5)"); - } - mSuggestionsList.setAdapter(adapter); - // now that we have an adapter, we can actually adjust the selection & scroll positions - if (mPresetSelection >= 0) { - boolean bTouchMode = mSuggestionsList.isInTouchMode(); - mSuggestionsList.setSelection(mPresetSelection); - mPresetSelection = -1; - } - if (DBG_LOG_TIMING == 1) { - dbgLogTiming("updateSuggestions(6)"); - } - } - - /** - * Utility for showing & hiding the suggestions list. This is also responsible for triggering - * animation, if any, at the right time. - * - * @param visible If true, show the suggestions, if false, hide them. - * @param animate If true, use animation. If false, "just do it." - */ - private void showSuggestions(boolean visible, boolean animate) { - if (visible) { - if (animate && (mSuggestionsList.getVisibility() != View.VISIBLE)) { - mSuggestionsList.startAnimation(mSuggestionsEntry); - } - mSuggestionsList.setVisibility(View.VISIBLE); - } else { - if (animate && (mSuggestionsList.getVisibility() != View.GONE)) { - mSuggestionsList.startAnimation(mSuggestionsExit); - } - mSuggestionsList.setVisibility(View.GONE); - } - } - - /** - * This helper class supports the suggestions list by allowing 3rd party (e.g. app) resources - * to be used in suggestions - */ - private static class SuggestionsCursorAdapter extends SimpleCursorAdapter { - - private Resources mProviderResources; - - public SuggestionsCursorAdapter(Context context, int layout, Cursor c, - String[] from, int[] to, Context providerContext) { - super(context, layout, c, from, to); - mProviderResources = providerContext.getResources(); - } - - /** - * Overriding this allows us to affect the way that an icon is loaded. Specifically, - * we can be more controlling about the resource path (and allow icons to come from other - * packages). - * - * @param v ImageView to receive an image - * @param value the value retrieved from the cursor - */ - @Override - public void setViewImage(ImageView v, String value) { - int resID; - Drawable img = null; - - try { - resID = Integer.parseInt(value); - if (resID != 0) { - img = mProviderResources.getDrawable(resID); - } - } catch (NumberFormatException nfe) { - // img = null; - } catch (NotFoundException e2) { - // img = null; - } - - // finally, set the image to whatever we've gotten - v.setImageDrawable(img); - } - - /** - * This method is overridden purely to provide a bit of protection against - * flaky content providers. - */ - @Override - /** - * @see android.widget.ListAdapter#getView(int, View, ViewGroup) - */ - public View getView(int position, View convertView, ViewGroup parent) { - try { - return super.getView(position, convertView, parent); - } catch (RuntimeException e) { - Log.w(LOG_TAG, "Search Suggestions cursor returned exception " + e.toString()); - // what can I return here? - View v = newView(mContext, mCursor, parent); - if (v != null) { - TextView tv = (TextView) v.findViewById(com.android.internal.R.id.text1); - tv.setText(e.toString()); - } - return v; - } - } - } - - /** - * Cleanly close the cursor being used by a ListView. Do this before replacing the adapter - * or before closing the ListView. - */ - private void discardListCursor(ListView list) { - CursorAdapter ca = getSuggestionsAdapter(list); - if (ca != null) { - Cursor c = ca.getCursor(); - if (c != null) { - ca.changeCursor(null); - } - } - } - - /** * Safely retrieve the suggestions cursor adapter from the ListView * * @param adapterView The ListView containing our adapter @@ -977,58 +603,6 @@ public class SearchDialog extends Dialog { } /** - * Get the query cursor for the search suggestions. - * - * @param query The search text entered (so far) - * @return Returns a cursor with suggestions, or null if no suggestions - */ - private Cursor getSuggestions(final SearchableInfo searchable, final String query) { - Cursor cursor = null; - if (searchable.getSuggestAuthority() != null) { - try { - StringBuilder uriStr = new StringBuilder("content://"); - uriStr.append(searchable.getSuggestAuthority()); - - // if content path provided, insert it now - final String contentPath = searchable.getSuggestPath(); - if (contentPath != null) { - uriStr.append('/'); - uriStr.append(contentPath); - } - - // append standard suggestion query path - uriStr.append('/' + SearchManager.SUGGEST_URI_PATH_QUERY); - - // inject query, either as selection args or inline - String[] selArgs = null; - if (searchable.getSuggestSelection() != null) { // if selection provided, use it - selArgs = new String[] {query}; - } else { - uriStr.append('/'); // no sel, use REST pattern - uriStr.append(Uri.encode(query)); - } - - // finally, make the query - if (DBG_LOG_TIMING == 1) { - dbgLogTiming("getSuggestions(1)"); - } - cursor = getContext().getContentResolver().query( - Uri.parse(uriStr.toString()), null, - searchable.getSuggestSelection(), selArgs, - null); - if (DBG_LOG_TIMING == 1) { - dbgLogTiming("getSuggestions(2)"); - } - } catch (RuntimeException e) { - Log.w(LOG_TAG, "Search Suggestions query returned exception " + e.toString()); - cursor = null; - } - } - - return cursor; - } - - /** * React to typing in the GO search button by refocusing to EditText. * Continue typing the query. */ @@ -1094,7 +668,10 @@ public class SearchDialog extends Dialog { * React to the user typing while the suggestions are focused. First, check for action * keys. If not handled, try refocusing regular characters into the EditText. In this case, * replace the query text (start typing fresh text). + * + * TODO: Move this code into mTextKeyListener, testing for a list entry being hilited */ + /* View.OnKeyListener mSuggestionsKeyListener = new View.OnKeyListener() { public boolean onKey(View v, int keyCode, KeyEvent event) { boolean handled = false; @@ -1108,6 +685,7 @@ public class SearchDialog extends Dialog { return handled; } }; + */ /** * Per UI design, we're going to "steer" any typed keystrokes back into the EditText @@ -1140,6 +718,9 @@ public class SearchDialog extends Dialog { /** * Update query text based on transitions in and out of suggestions list. */ + /* + * TODO - figure out if this logic is required for the autocomplete text view version + OnFocusChangeListener mSuggestFocusListener = new OnFocusChangeListener() { public void onFocusChange(View v, boolean hasFocus) { // also guard against possible race conditions (late arrival after dismiss) @@ -1170,24 +751,9 @@ public class SearchDialog extends Dialog { } }; + */ /** - * Update query text based on movement of selection in/out of suggestion list - */ - OnItemSelectedListener mSuggestSelectedListener = new OnItemSelectedListener() { - public void onItemSelected(AdapterView parent, View view, int position, long id) { - // Update query text while user navigates through suggestions list - // also guard against possible race conditions (late arrival after dismiss) - if (mSearchable != null && position >= 0 && mSuggestionsList.isFocused()) { - jamSuggestionQuery(true, parent, position); - } - } - - // No action needed on this callback - public void onNothingSelected(AdapterView parent) { } - }; - - /** * This is the listener for the ACTION_CLOSE_SYSTEM_DIALOGS intent. It's an indication that * we should close ourselves immediately, in order to allow a higher-priority UI to take over * (e.g. phone call received). @@ -1225,20 +791,6 @@ public class SearchDialog extends Dialog { }; /** - * Listener for layout changes in the main layout. I use this to dynamically clean up - * the layout of the dropdown and make it "pixel perfect." - */ - private ViewTreeObserver.OnGlobalLayoutListener mGlobalLayoutListener - = new ViewTreeObserver.OnGlobalLayoutListener() { - - // It's very important that layoutSuggestionsList() does not reset - // the values more than once, or this becomes an infinite loop. - public void onGlobalLayout() { - layoutSuggestionsList(); - } - }; - - /** * Various ways to launch searches */ @@ -1333,7 +885,7 @@ public class SearchDialog extends Dialog { * @param jamQuery True means to set the query, false means to reset it to the user's choice */ private void jamSuggestionQuery(boolean jamQuery, AdapterView<?> parent, int position) { - mNonUserQuery = true; // disables any suggestions processing + mSuggestionsAdapter.setNonUserQuery(true); // disables any suggestions processing if (jamQuery) { CursorAdapter ca = getSuggestionsAdapter(parent); Cursor c = ca.getCursor(); @@ -1356,7 +908,10 @@ public class SearchDialog extends Dialog { } if (jamText != null) { mSearchTextField.setText(jamText); - mSearchTextField.selectAll(); + /* mSearchTextField.selectAll(); */ // this didn't work anyway in the old UI + // TODO this is only needed in the model where we have a selection in the ACTV + // and in the dropdown at the same time. + mSearchTextField.setSelection(jamText.length()); } } } else { @@ -1372,7 +927,10 @@ public class SearchDialog extends Dialog { mSearchTextField.selectAll(); } } - mNonUserQuery = false; + // TODO because the new query is (not) processed in another thread, we can't just + // take away this flag (yet). The better solution here is going to require a new API + // in AutoCompleteTextView which allows us to change the text w/o changing the suggestions. +// mSuggestionsAdapter.setNonUserQuery(false); } /** @@ -1418,18 +976,6 @@ public class SearchDialog extends Dialog { } /** - * Handler for clicks in the suggestions list - */ - private OnItemClickListener mSuggestionsListItemClickListener = new OnItemClickListener() { - public void onItemClick(AdapterView parent, View v, int position, long id) { - // this guard protects against possible race conditions (late arrival of click) - if (mSearchable != null) { - launchSuggestion(parent, position); - } - } - }; - - /** * Shared code for launching a query from a suggestion. * * @param av The AdapterView (really a ListView) containing the suggestions @@ -1439,6 +985,16 @@ public class SearchDialog extends Dialog { */ private boolean launchSuggestion(AdapterView<?> av, int position) { CursorAdapter ca = getSuggestionsAdapter(av); + return launchSuggestion(ca, position); + } + + /** + * Shared code for launching a query from a suggestion. + * @param ca The cursor adapter containing the suggestions + * @param position The suggestion we'll be launching from + * @return true if a successful launch, false if could not (e.g. bad position) + */ + private boolean launchSuggestion(CursorAdapter ca, int position) { Cursor c = ca.getCursor(); if ((c != null) && c.moveToPosition(position)) { setupSuggestionIntent(c, mSearchable); @@ -1457,33 +1013,6 @@ public class SearchDialog extends Dialog { } /** - * Manually adjust suggestions list into its perfectly-tweaked position. - * - * NOTE: This MUST not adjust the parameters if they are already set correctly, - * or you create an infinite loop via the ViewTreeObserver.OnGlobalLayoutListener callback. - */ - private void layoutSuggestionsList() { - final int FUDGE_SUGG_X = 1; - final int FUDGE_SUGG_WIDTH = 2; - - int[] itemLoc = new int[2]; - mSearchTextField.getLocationOnScreen(itemLoc); - int x,width; - x = itemLoc[0] + FUDGE_SUGG_X; - width = mSearchTextField.getMeasuredWidth() + FUDGE_SUGG_WIDTH; - - // now set params and relayout - ViewGroup.MarginLayoutParams lp; - lp = (ViewGroup.MarginLayoutParams) mSuggestionsList.getLayoutParams(); - boolean changing = (lp.width != width) || (lp.leftMargin != x); - if (changing) { - lp.leftMargin = x; - lp.width = width; - mSuggestionsList.setLayoutParams(lp); - } - } - - /** * When a particular suggestion has been selected, perform the various lookups required * to use the suggestion. This includes checking the cursor for suggestion-specific data, * and/or falling back to the XML for defaults; It also creates REST style Uri data when @@ -1585,6 +1114,301 @@ public class SearchDialog extends Dialog { } return result; } + + /** + * Support for AutoCompleteTextView-based suggestions + */ + /** + * This class provides the filtering-based interface to suggestions providers. + * It is hardwired in a couple of places to support GoogleSearch - for example, it supports + * two-line suggestions, but it does not support icons. + */ + private static class SuggestionsAdapter extends SimpleCursorAdapter { + private final String TAG = "SuggestionsAdapter"; + + SearchableInfo mSearchable; + private Resources mProviderResources; + + // These private variables are shared by the filter thread and must be protected + private WeakReference<Cursor> mRecentCursor = new WeakReference<Cursor>(null); + private boolean mNonUserQuery = false; + + public SuggestionsAdapter(Context context, SearchableInfo searchable) { + super(context, -1, null, null, null); + mSearchable = searchable; + + // set up provider resources (gives us icons, etc.) + Context activityContext = mSearchable.getActivityContext(mContext); + Context providerContext = mSearchable.getProviderContext(mContext, activityContext); + mProviderResources = providerContext.getResources(); + } + + /** + * Set this field (temporarily!) to disable suggestions updating. This allows us + * to change the string in the text view without changing the suggestions list. + */ + public void setNonUserQuery(boolean nonUserQuery) { + synchronized (this) { + mNonUserQuery = nonUserQuery; + } + } + + public boolean getNonUserQuery() { + synchronized (this) { + return mNonUserQuery; + } + } + + /** + * Use the search suggestions provider to obtain a live cursor. This will be called + * in a worker thread, so it's OK if the query is slow (e.g. round trip for suggestions). + * The results will be processed in the UI thread and changeCursor() will be called. + * + * In order to provide the Search Mgr functionality of seeing your query change as you + * scroll through the list, we have to be able to jam new text into the string without + * retriggering the suggestions. We do that here via the "nonUserQuery" flag. In that + * case we simply return the existing cursor. + * + * TODO: Dianne suggests that this should simply be promoted into an AutoCompleteTextView + * behavior (perhaps optionally). + * + * TODO: The "nonuserquery" logic has a race condition because it happens in another thread. + * This also needs to be fixed. + */ + @Override + public Cursor runQueryOnBackgroundThread(CharSequence constraint) { + String query = (constraint == null) ? "" : constraint.toString(); + Cursor c = null; + synchronized (this) { + if (mNonUserQuery) { + c = mRecentCursor.get(); + mNonUserQuery = false; + } + } + if (c == null) { + c = getSuggestions(mSearchable, query); + synchronized (this) { + mRecentCursor = new WeakReference<Cursor>(c); + } + } + return c; + } + + /** + * Overriding changeCursor() allows us to change not only the cursor, but by sampling + * the cursor's columns, the actual display characteristics of the list. + */ + @Override + public void changeCursor(Cursor c) { + + // first, check for various conditions that disqualify this cursor + if ((c == null) || (c.getCount() == 0)) { + // no cursor, or cursor with no data + changeCursorAndColumns(null, null, null); + if (c != null) { + c.close(); + } + return; + } + + // check cursor before trying to create list views from it + int colId = c.getColumnIndex("_id"); + int col1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_1); + int col2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_TEXT_2); + int colIc1 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_1); + int colIc2 = c.getColumnIndex(SearchManager.SUGGEST_COLUMN_ICON_2); + + boolean minimal = (colId >= 0) && (col1 >= 0); + boolean hasIcons = (colIc1 >= 0) && (colIc2 >= 0); + boolean has2Lines = col2 >= 0; + + if (minimal) { + int layout; + String[] from; + int[] to; + + if (hasIcons) { + if (has2Lines) { + layout = com.android.internal.R.layout.search_dropdown_item_icons_2line; + from = TWO_LINE_ICONS_FROM; + to = TWO_LINE_ICONS_TO; + } else { + layout = com.android.internal.R.layout.search_dropdown_item_icons_1line; + from = ONE_LINE_ICONS_FROM; + to = ONE_LINE_ICONS_TO; + } + } else { + if (has2Lines) { + layout = com.android.internal.R.layout.search_dropdown_item_2line; + from = TWO_LINE_FROM; + to = TWO_LINE_TO; + } else { + layout = com.android.internal.R.layout.search_dropdown_item_1line; + from = ONE_LINE_FROM; + to = ONE_LINE_TO; + } + } + // Now actually set up the cursor, columns, and the list view + changeCursorAndColumns(c, from, to); + setViewResource(layout); + } else { + // Provide some help for developers instead of just silently discarding + Log.w(LOG_TAG, "Suggestions cursor discarded due to missing required columns."); + changeCursorAndColumns(null, null, null); + c.close(); + } + if ((colIc1 >= 0) != (colIc2 >= 0)) { + Log.w(LOG_TAG, "Suggestion icon column(s) discarded, must be 0 or 2 columns."); + } + } + + /** + * Overriding this allows us to write the selected query back into the box. + * NOTE: This is a vastly simplified version of SearchDialog.jamQuery() and does + * not universally support the search API. But it is sufficient for Google Search. + */ + @Override + public CharSequence convertToString(Cursor cursor) { + CharSequence result = null; + if (cursor != null) { + int column = cursor.getColumnIndex(SearchManager.SUGGEST_COLUMN_QUERY); + if (column >= 0) { + final String query = cursor.getString(column); + if (query != null) { + result = query; + } + } + } + return result; + } + + /** + * Get the query cursor for the search suggestions. + * + * TODO this is functionally identical to the version in SearchDialog.java. Perhaps it + * could be hoisted into SearchableInfo or some other shared spot. + * + * @param query The search text entered (so far) + * @return Returns a cursor with suggestions, or null if no suggestions + */ + private Cursor getSuggestions(final SearchableInfo searchable, final String query) { + Cursor cursor = null; + if (searchable.getSuggestAuthority() != null) { + try { + StringBuilder uriStr = new StringBuilder("content://"); + uriStr.append(searchable.getSuggestAuthority()); + + // if content path provided, insert it now + final String contentPath = searchable.getSuggestPath(); + if (contentPath != null) { + uriStr.append('/'); + uriStr.append(contentPath); + } + + // append standard suggestion query path + uriStr.append('/' + SearchManager.SUGGEST_URI_PATH_QUERY); + + // inject query, either as selection args or inline + String[] selArgs = null; + if (searchable.getSuggestSelection() != null) { // use selection if provided + selArgs = new String[] {query}; + } else { + uriStr.append('/'); // no sel, use REST pattern + uriStr.append(Uri.encode(query)); + } + + // finally, make the query + cursor = mContext.getContentResolver().query( + Uri.parse(uriStr.toString()), null, + searchable.getSuggestSelection(), selArgs, + null); + } catch (RuntimeException e) { + Log.w(TAG, "Search Suggestions query returned exception " + e.toString()); + cursor = null; + } + } + + return cursor; + } + + /** + * Overriding this allows us to affect the way that an icon is loaded. Specifically, + * we can be more controlling about the resource path (and allow icons to come from other + * packages). + * + * TODO: This is 100% identical to the version in SearchDialog.java + * + * @param v ImageView to receive an image + * @param value the value retrieved from the cursor + */ + @Override + public void setViewImage(ImageView v, String value) { + int resID; + Drawable img = null; + + try { + resID = Integer.parseInt(value); + if (resID != 0) { + img = mProviderResources.getDrawable(resID); + } + } catch (NumberFormatException nfe) { + // img = null; + } catch (NotFoundException e2) { + // img = null; + } + + // finally, set the image to whatever we've gotten + v.setImageDrawable(img); + } + + /** + * This method is overridden purely to provide a bit of protection against + * flaky content providers. + * + * TODO: This is 100% identical to the version in SearchDialog.java + * + * @see android.widget.ListAdapter#getView(int, View, ViewGroup) + */ + @Override + public View getView(int position, View convertView, ViewGroup parent) { + try { + return super.getView(position, convertView, parent); + } catch (RuntimeException e) { + Log.w(TAG, "Search Suggestions cursor returned exception " + e.toString()); + // what can I return here? + View v = newView(mContext, mCursor, parent); + if (v != null) { + TextView tv = (TextView) v.findViewById(com.android.internal.R.id.text1); + tv.setText(e.toString()); + } + return v; + } + } + + } + + /** + * Implements OnItemClickListener + */ + public void onItemClick(AdapterView<?> parent, View view, int position, long id) { +// Log.d(LOG_TAG, "onItemClick() position " + position); + launchSuggestion(mSuggestionsAdapter, position); + } + + /** + * Implements OnItemSelectedListener + */ + public void onItemSelected(AdapterView<?> parent, View view, int position, long id) { +// Log.d(LOG_TAG, "onItemSelected() position " + position); + jamSuggestionQuery(true, parent, position); + } + + /** + * Implements OnItemSelectedListener + */ + public void onNothingSelected(AdapterView<?> parent) { +// Log.d(LOG_TAG, "onNothingSelected()"); + } /** * Debugging Support diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index 5f25b90..0a37e81 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -739,6 +739,14 @@ import android.view.KeyEvent; * <td align="center">No</td> * </tr> * + * <tr><th>android:inputType</th> + * <td>If provided, supplies a hint about the type of search text the user will be + * entering. For most searches, in which free form text is expected, this attribute + * need not be provided. Suitable values for this attribute are described in the + * <a href="../R.attr.html#inputType">inputType</a> attribute.</td> + * <td align="center">No</td> + * </tr> + * * </tbody> * </table> * diff --git a/core/java/android/bluetooth/BluetoothA2dp.java b/core/java/android/bluetooth/BluetoothA2dp.java index d6ea889..022a87c 100644 --- a/core/java/android/bluetooth/BluetoothA2dp.java +++ b/core/java/android/bluetooth/BluetoothA2dp.java @@ -90,7 +90,7 @@ public class BluetoothA2dp { } /** Initiate a connection to an A2DP sink. - * Listen for A2DP_SINK_STATE_CHANGED_ACTION to find out when the + * Listen for SINK_STATE_CHANGED_ACTION to find out when the * connection is completed. * @param address Remote BT address. * @return Result code, negative indicates an immediate error. @@ -106,7 +106,7 @@ public class BluetoothA2dp { } /** Initiate disconnect from an A2DP sink. - * Listen for A2DP_SINK_STATE_CHANGED_ACTION to find out when + * Listen for SINK_STATE_CHANGED_ACTION to find out when * disconnect is completed. * @param address Remote BT address. * @return Result code, negative indicates an immediate error. diff --git a/core/java/android/content/AbstractTableMerger.java b/core/java/android/content/AbstractTableMerger.java index e1a484e..5511ff6 100644 --- a/core/java/android/content/AbstractTableMerger.java +++ b/core/java/android/content/AbstractTableMerger.java @@ -477,7 +477,7 @@ public abstract class AbstractTableMerger private void fullyDeleteRowsWithSyncId(String syncId, String account, SyncResult syncResult) { final String[] selectionArgs = new String[]{syncId, account}; // delete the rows explicitly so that the delete operation can be overridden - Cursor c = mDb.query(mTable, new String[]{"_id"}, SELECT_BY_ID_AND_ACCOUNT, + Cursor c = mDb.query(mTable, getDeleteRowProjection(), SELECT_BY_ID_AND_ACCOUNT, selectionArgs, null, null, null); try { c.moveToFirst(); @@ -494,6 +494,16 @@ public abstract class AbstractTableMerger } /** + * Provides the projection used by + * {@link AbstractTableMerger#deleteRow(android.database.Cursor)}. + * This should be overridden if the deleteRow implementation requires + * additional columns. + */ + protected String[] getDeleteRowProjection() { + return new String[]{"_id"}; + } + + /** * Converts cursor into a Map, using the correct types for the values. */ protected void cursorRowToContentValues(Cursor cursor, ContentValues map) { diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index dc75748..e2d7097 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -100,10 +100,12 @@ public class Camera { /** * Reconnect to the camera after passing it to MediaRecorder. To save - * setup/teardown time, a client of Camara can pass an initialized Camera + * setup/teardown time, a client of Camera can pass an initialized Camera * object to a MediaRecorder to use for video recording. Once the * MediaRecorder is done with the Camera, this method can be used to - * re-establish a connection with the camera hardware. + * re-establish a connection with the camera hardware. NOTE: The Camera + * object must first be unlocked by the process that owns it before it + * can be connected to another proces. * * @throws IOException if the method fails. * @@ -113,6 +115,34 @@ public class Camera { public native final void reconnect() throws IOException; /** + * Lock the camera to prevent other processes from accessing it. To save + * setup/teardown time, a client of Camera can pass an initialized Camera + * object to another process. This method is used to re-lock the Camera + * object prevent other processes from accessing it. By default, the + * Camera object is locked. Locking it again from the same process will + * have no effect. Attempting to lock it from another process if it has + * not been unlocked will fail. + * Returns 0 if lock was successful. + * + * FIXME: Unhide after approval + * @hide + */ + public native final int lock(); + + /** + * Unlock the camera to allow aother process to access it. To save + * setup/teardown time, a client of Camera can pass an initialized Camera + * object to another process. This method is used to unlock the Camera + * object before handing off the Camera object to the other process. + + * Returns 0 if unlock was successful. + * + * FIXME: Unhide after approval + * @hide + */ + public native final int unlock(); + + /** * Sets the SurfaceHolder to be used for a picture preview. If the surface * changed since the last call, the screen will blank. Nothing happens * if the same surface is re-set. @@ -152,6 +182,14 @@ public class Camera { public native final void stopPreview(); /** + * Return current preview state. + * + * FIXME: Unhide before release + * @hide + */ + public native final boolean previewEnabled(); + + /** * Can be called at any time to instruct the camera to use a callback for * each preview frame in addition to displaying it. * @@ -242,7 +280,9 @@ public class Camera { }; /** - * Registers a callback to be invoked when the auto focus responds. + * Starts auto-focus function and registers a callback function to + * run when camera is focused. Only valid after startPreview() has + * been called. * * @param cb the callback to run */ @@ -525,6 +565,58 @@ public class Camera { } /** + * Sets the dimensions for EXIF thumbnails. + * + * @param width the width of the thumbnail, in pixels + * @param height the height of the thumbnail, in pixels + * + * FIXME: unhide before release + * @hide + */ + public void setThumbnailSize(int width, int height) { + set("jpeg-thumbnail-width", width); + set("jpeg-thumbnail-height", height); + } + + /** + * Returns the dimensions for EXIF thumbnail + * + * @return a Size object with the height and width setting + * for the EXIF thumbnails + * + * FIXME: unhide before release + * @hide + */ + public Size getThumbnailSize() { + return new Size(getInt("jpeg-thumbnail-width"), + getInt("jpeg-thumbnail-height")); + } + + /** + * Sets the quality of the EXIF thumbnail + * + * @param quality the JPEG quality of the EXIT thumbnail + * + * FIXME: unhide before release + * @hide + */ + public void setThumbnailQuality(int quality) { + set("jpeg-thumbnail-quality", quality); + } + + /** + * Returns the quality setting for the EXIF thumbnail + * + * @return the JPEG quality setting of the EXIF thumbnail + * + * FIXME: unhide before release + * @hide + */ + public int getThumbnailQuality() { + return getInt("jpeg-thumbnail-quality"); + } + + /** * Sets the rate at which preview frames are received. * * @param fps the frame rate (frames per second) @@ -547,7 +639,7 @@ public class Camera { * Sets the image format for preview pictures. * * @param pixel_format the desired preview picture format - * (<var>PixelFormat.YCbCr_422_SP</var>, + * (<var>PixelFormat.YCbCr_420_SP</var>, * <var>PixelFormat.RGB_565</var>, or * <var>PixelFormat.JPEG</var>) * @see android.graphics.PixelFormat @@ -604,7 +696,7 @@ public class Camera { * Sets the image format for pictures. * * @param pixel_format the desired picture format - * (<var>PixelFormat.YCbCr_422_SP</var>, + * (<var>PixelFormat.YCbCr_420_SP</var>, * <var>PixelFormat.RGB_565</var>, or * <var>PixelFormat.JPEG</var>) * @see android.graphics.PixelFormat @@ -630,6 +722,7 @@ public class Camera { private String cameraFormatForPixelFormat(int pixel_format) { switch(pixel_format) { case PixelFormat.YCbCr_422_SP: return "yuv422sp"; + case PixelFormat.YCbCr_420_SP: return "yuv420sp"; case PixelFormat.RGB_565: return "rgb565"; case PixelFormat.JPEG: return "jpeg"; default: return null; @@ -643,6 +736,9 @@ public class Camera { if (format.equals("yuv422sp")) return PixelFormat.YCbCr_422_SP; + if (format.equals("yuv420sp")) + return PixelFormat.YCbCr_420_SP; + if (format.equals("rgb565")) return PixelFormat.RGB_565; diff --git a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java index 40c03cd..5a85c66 100644 --- a/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java +++ b/core/java/android/inputmethodservice/IInputMethodSessionWrapper.java @@ -91,7 +91,7 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub case DO_UPDATE_SELECTION: { HandlerCaller.SomeArgs args = (HandlerCaller.SomeArgs)msg.obj; mInputMethodSession.updateSelection(args.argi1, args.argi2, - args.argi3, args.argi4); + args.argi3, args.argi4, args.argi5, args.argi6); mCaller.recycleArgs(args); return; } @@ -135,9 +135,10 @@ class IInputMethodSessionWrapper extends IInputMethodSession.Stub } public void updateSelection(int oldSelStart, int oldSelEnd, - int newSelStart, int newSelEnd) { - mCaller.executeOrSendMessage(mCaller.obtainMessageIIII(DO_UPDATE_SELECTION, - oldSelStart, oldSelEnd, newSelStart, newSelEnd)); + int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { + mCaller.executeOrSendMessage(mCaller.obtainMessageIIIIII(DO_UPDATE_SELECTION, + oldSelStart, oldSelEnd, newSelStart, newSelEnd, + candidatesStart, candidatesEnd)); } public void updateCursor(Rect newCursor) { diff --git a/core/java/android/inputmethodservice/InputMethodService.java b/core/java/android/inputmethodservice/InputMethodService.java index 9ebf127..0588bea 100644 --- a/core/java/android/inputmethodservice/InputMethodService.java +++ b/core/java/android/inputmethodservice/InputMethodService.java @@ -48,6 +48,132 @@ import android.widget.FrameLayout; * which final implementations can derive from and customize. See the * base class {@link AbstractInputMethodService} and the {@link InputMethod} * interface for more information on the basics of writing input methods. + * + * <p>An input method has significant discretion in how it goes about its + * work: the {@link android.inputmethodservice.InputMethodService} provides + * a basic framework for standard UI elements (input view, candidates view, + * and running in fullscreen mode), but it is up to a particular implementor + * to decide how to use them. For example, one input method could implement + * an input area with a keyboard, another could allow the user to draw text, + * while a third could have no input area (and thus not be visible to the + * user) but instead listen to audio and perform text to speech conversion.</p> + * + * <p>In the implementation provided here, all of these elements are placed + * together in a single window managed by the InputMethodService. It will + * execute callbacks as it needs information about them, and provides APIs for + * programmatic control over them. They layout of these elements is explicitly + * defined:</p> + * + * <ul> + * <li>The soft input view, if available, is placed at the bottom of the + * screen. + * <li>The candidates view, if currently shown, is placed above the soft + * input view. + * <li>If not running fullscreen, the application is moved or resized to be + * above these views; if running fullscreen, the window will completely cover + * the application and its top part will contain the extract text of what is + * currently being edited by the application. + * </ul> + * + * + * <a name="SoftInputView"></a> + * <h3>Soft Input View</h3> + * + * <p>Central to most input methods is the soft input view. This is where most + * user interaction occurs: pressing on soft keys, drawing characters, or + * however else your input method wants to generate text. Most implementations + * will simply have their own view doing all of this work, and return a new + * instance of it when {@link #onCreateInputView()} is called. At that point, + * as long as the input view is visible, you will see user interaction in + * that view and can call back on the InputMethodService to interact with the + * application as appropriate.</p> + * + * <p>There are some situations where you want to decide whether or not your + * soft input view should be shown to the user. This is done by implementing + * the {@link #onEvaluateInputViewShown()} to return true or false based on + * whether it should be shown in the current environment. If any of your + * state has changed that may impact this, call + * {@link #updateInputViewShown()} to have it re-evaluated. The default + * implementation always shows the input view unless there is a hard + * keyboard available, which is the appropriate behavior for most input + * methods.</p> + * + * + * <a name="CandidatesView"></a> + * <h3>Candidates View</h3> + * + * <p>Often while the user is generating raw text, an input method wants to + * provide them with a list of possible interpretations of that text that can + * be selected for use. This is accomplished with the candidates view, and + * like the soft input view you implement {@link #onCreateCandidatesView()} + * to instantiate your own view implementing your candidates UI.</p> + * + * <p>Management of the candidates view is a little different than the input + * view, because the candidates view tends to be more transient, being shown + * only when there are possible candidates for the current text being entered + * by the user. To control whether the candidates view is shown, you use + * {@link #setCandidatesViewShown(boolean)}. Note that because the candidate + * view tends to be shown and hidden a lot, it does not impact the application + * UI in the same way as the soft input view: it will never cause application + * windows to resize, only cause them to be panned if needed for the user to + * see the current focus.</p> + * + * + * <a name="FullscreenMode"></a> + * <h3>Fullscreen Mode</h3> + * + * <p>Sometimes your input method UI is too large to integrate with the + * application UI, so you just want to take over the screen. This is + * accomplished by switching to full-screen mode, causing the input method + * window to fill the entire screen and add its own "extracted text" editor + * showing the user the text that is being typed. Unlike the other UI elements, + * there is a standard implementation for the extract editor that you should + * not need to change. The editor is placed at the top of the IME, above the + * input and candidates views.</p> + * + * <p>Similar to the input view, you control whether the IME is running in + * fullscreen mode by implementing {@link #onEvaluateFullscreenMode()} + * to return true or false based on + * whether it should be fullscreen in the current environment. If any of your + * state has changed that may impact this, call + * {@link #updateFullscreenMode()} to have it re-evaluated. The default + * implementation selects fullscreen mode when the screen is in a landscape + * orientation, which is appropriate behavior for most input methods that have + * a significant input area.</p> + * + * <p>When in fullscreen mode, you have some special requirements because the + * user can not see the application UI. In particular, you should implement + * {@link #onDisplayCompletions(CompletionInfo[])} to show completions + * generated by your application, typically in your candidates view like you + * would normally show candidates. + * + * + * <a name="GeneratingText"></a> + * <h3>Generating Text</h3> + * + * <p>The key part of an IME is of course generating text for the application. + * This is done through calls to the + * {@link android.view.inputmethod.InputConnection} interface to the + * application, which can be retrieved from {@link #getCurrentInputConnection()}. + * This interface allows you to generate raw key events or, if the target + * supports it, directly edit in strings of candidates and committed text.</p> + * + * <p>Information about what the target is expected and supports can be found + * through the {@link android.view.inputmethod.EditorInfo} class, which is + * retrieved with {@link #getCurrentInputEditorInfo()} method. The most + * important part of this is {@link android.view.inputmethod.EditorInfo#inputType + * EditorInfo.inputType}; in particular, if this is + * {@link android.view.inputmethod.EditorInfo#TYPE_NULL EditorInfo.TYPE_NULL}, + * then the target does not support complex edits and you need to only deliver + * raw key events to it. An input method will also want to look at other + * values here, to for example detect password mode, auto complete text views, + * phone number entry, etc.</p> + * + * <p>When the user switches between input targets, you will receive calls to + * {@link #onFinishInput()} and {@link #onStartInput(EditorInfo, boolean)}. + * You can use these to reset and initialize your input state for the current + * target. For example, you will often want to clear any input state, and + * update a soft keyboard to be appropriate for the new inputType.</p> */ public class InputMethodService extends AbstractInputMethodService { static final String TAG = "InputMethodService"; @@ -68,7 +194,7 @@ public class InputMethodService extends AbstractInputMethodService { InputBinding mInputBinding; InputConnection mInputConnection; boolean mInputStarted; - EditorInfo mInputInfo; + EditorInfo mInputEditorInfo; boolean mShowInputRequested; boolean mShowCandidatesRequested; @@ -210,12 +336,13 @@ public class InputMethodService extends AbstractInputMethodService { * InputMethodService.onUpdateSelection()}. */ public void updateSelection(int oldSelStart, int oldSelEnd, - int newSelStart, int newSelEnd) { + int newSelStart, int newSelEnd, + int candidatesStart, int candidatesEnd) { if (!isEnabled()) { return; } InputMethodService.this.onUpdateSelection(oldSelStart, oldSelEnd, - newSelStart, newSelEnd); + newSelStart, newSelEnd, candidatesStart, candidatesEnd); } /** @@ -303,6 +430,7 @@ public class InputMethodService extends AbstractInputMethodService { Context.LAYOUT_INFLATER_SERVICE); mWindow = new SoftInputWindow(this); initViews(); + mWindow.getWindow().setLayout(FILL_PARENT, WRAP_CONTENT); } void initViews() { @@ -384,8 +512,8 @@ public class InputMethodService extends AbstractInputMethodService { return mInputStarted; } - public EditorInfo getCurrentInputInfo() { - return mInputInfo; + public EditorInfo getCurrentInputEditorInfo() { + return mInputEditorInfo; } /** @@ -459,14 +587,14 @@ public class InputMethodService extends AbstractInputMethodService { int[] loc = mTmpLocation; if (mInputFrame.getVisibility() == View.VISIBLE) { mInputFrame.getLocationInWindow(loc); - outInsets.contentTopInsets = loc[1]; + } else { + loc[1] = 0; } + outInsets.contentTopInsets = loc[1]; if (mCandidatesFrame.getVisibility() == View.VISIBLE) { mCandidatesFrame.getLocationInWindow(loc); - outInsets.visibleTopInsets = loc[1]; - } else { - outInsets.visibleTopInsets = loc[1]; } + outInsets.visibleTopInsets = loc[1]; outInsets.touchableInsets = Insets.TOUCHABLE_INSETS_VISIBLE; } @@ -712,7 +840,7 @@ public class InputMethodService extends AbstractInputMethodService { if (doShowInput) { if (mInputStarted) { if (DEBUG) Log.v(TAG, "showWindow: starting input view"); - onStartInputView(mInputInfo, false); + onStartInputView(mInputEditorInfo, false); } startExtractingText(); } @@ -744,11 +872,11 @@ public class InputMethodService extends AbstractInputMethodService { void doStartInput(EditorInfo attribute, boolean restarting) { mInputStarted = true; - mInputInfo = attribute; + mInputEditorInfo = attribute; onStartInput(attribute, restarting); if (mWindowVisible) { if (mWindowCreated) { - onStartInputView(mInputInfo, restarting); + onStartInputView(mInputEditorInfo, restarting); } startExtractingText(); } @@ -795,7 +923,8 @@ public class InputMethodService extends AbstractInputMethodService { * the extract text, if it is being shown. */ public void onUpdateSelection(int oldSelStart, int oldSelEnd, - int newSelStart, int newSelEnd) { + int newSelStart, int newSelEnd, + int candidatesStart, int candidatesEnd) { if (mExtractEditText != null && mExtractedText != null) { final int off = mExtractedText.startOffset; mExtractEditText.setSelection(newSelStart-off, newSelEnd-off); @@ -821,10 +950,22 @@ public class InputMethodService extends AbstractInputMethodService { } public boolean onKeyDown(int keyCode, KeyEvent event) { - if (mWindowVisible && event.getKeyCode() == KeyEvent.KEYCODE_BACK + if (event.getKeyCode() == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { - dismissSoftInput(); - return true; + if (mShowInputRequested) { + // If the soft input area is shown, back closes it and we + // consume the back key. + dismissSoftInput(); + return true; + } + if (mShowCandidatesRequested) { + // If the candidates are shown, we just want to make sure + // they are now hidden but otherwise let the app execute + // the back. + // XXX this needs better interaction with the soft input + // implementation. + //setCandidatesViewShown(false); + } } return false; } @@ -857,8 +998,8 @@ public class InputMethodService extends AbstractInputMethodService { if (mExtractedText != null) { mExtractEditText.setExtractedText(mExtractedText); } - mExtractEditText.setInputType(getCurrentInputInfo().inputType); - mExtractEditText.setHint(mInputInfo.hintText); + mExtractEditText.setInputType(getCurrentInputEditorInfo().inputType); + mExtractEditText.setHint(mInputEditorInfo.hintText); } } } diff --git a/core/java/android/inputmethodservice/Keyboard.java b/core/java/android/inputmethodservice/Keyboard.java index 75a2911..cfd3188 100755 --- a/core/java/android/inputmethodservice/Keyboard.java +++ b/core/java/android/inputmethodservice/Keyboard.java @@ -438,7 +438,6 @@ public class Keyboard { } } - /** * Returns the square of the distance between the center of the key and the given point. * @param x the x-coordinate of the point @@ -446,9 +445,9 @@ public class Keyboard { * @return the square of the distance of the point from the center of the key */ public int squaredDistanceFrom(int x, int y) { - float xDist = Math.abs((this.x + this.x + width) / 2f - x); - float yDist = Math.abs((this.y + this.y + height) / 2f - y); - return (int) (xDist * xDist + yDist * yDist); + int xDist = this.x + width / 2 - x; + int yDist = this.y + height / 2 - y; + return xDist * xDist + yDist * yDist; } /** @@ -749,7 +748,8 @@ public class Keyboard { if (value.type == TypedValue.TYPE_DIMENSION) { return a.getDimensionPixelOffset(index, defValue); } else if (value.type == TypedValue.TYPE_FRACTION) { - return (int) a.getFraction(index, base, base, defValue); + // Round it to avoid values like 47.9999 from getting truncated + return Math.round(a.getFraction(index, base, base, defValue)); } return defValue; } diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index 56473da..3b5d741 100755 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -24,6 +24,7 @@ import android.content.res.TypedArray; import android.graphics.Canvas; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.Typeface; import android.graphics.Paint.Align; import android.graphics.drawable.Drawable; import android.inputmethodservice.Keyboard.Key; @@ -37,6 +38,7 @@ import android.view.Gravity; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; +import android.view.ViewConfiguration; import android.view.ViewGroup.LayoutParams; import android.widget.Button; import android.widget.PopupWindow; @@ -132,6 +134,7 @@ public class KeyboardView extends View implements View.OnClickListener { private static final int MSG_REMOVE_PREVIEW = 1; private static final int MSG_REPEAT = 2; + private static final int MSG_LONGPRESS = 3; private int mVerticalCorrection; private int mProximityThreshold; @@ -178,6 +181,7 @@ public class KeyboardView extends View implements View.OnClickListener { private static final int REPEAT_INTERVAL = 50; // ~20 keys per second private static final int REPEAT_START_DELAY = 400; + private static final int LONGPRESS_TIMEOUT = ViewConfiguration.getLongPressTimeout(); private Vibrator mVibrator; private long[] mVibratePattern = new long[] {1, 20}; @@ -206,6 +210,9 @@ public class KeyboardView extends View implements View.OnClickListener { sendMessageDelayed(repeat, REPEAT_INTERVAL); } break; + case MSG_LONGPRESS: + openPopupIfRequired((MotionEvent) msg.obj); + break; } } @@ -308,27 +315,28 @@ public class KeyboardView extends View implements View.OnClickListener { @Override public boolean onFling(MotionEvent me1, MotionEvent me2, float velocityX, float velocityY) { - if (velocityX > 400 && Math.abs(velocityY) < 400) { + final float absX = Math.abs(velocityX); + final float absY = Math.abs(velocityY); + if (velocityX > 500 && absY < absX) { swipeRight(); return true; - } else if (velocityX < -400 && Math.abs(velocityY) < 400) { + } else if (velocityX < -500 && absY < absX) { swipeLeft(); return true; - } else if (velocityY < -400 && Math.abs(velocityX) < 400) { + } else if (velocityY < -500 && absX < absY) { swipeUp(); return true; - } else if (velocityY > 400 && Math.abs(velocityX) < 400) { + } else if (velocityY > 500 && absX < 200) { swipeDown(); return true; + } else if (absX > 800 || absY > 800) { + return true; } return false; } - - @Override - public void onLongPress(MotionEvent me) { - openPopupIfRequired(me); - } }); + + mGestureDetector.setIsLongpressEnabled(false); } public void setOnKeyboardActionListener(OnKeyboardActionListener listener) { @@ -351,6 +359,9 @@ public class KeyboardView extends View implements View.OnClickListener { * @param keyboard the keyboard to display in this view */ public void setKeyboard(Keyboard keyboard) { + if (mKeyboard != null) { + showPreview(NOT_A_KEY); + } mKeyboard = keyboard; requestLayout(); invalidate(); @@ -518,10 +529,10 @@ public class KeyboardView extends View implements View.OnClickListener { // For characters, use large font. For labels like "Done", use small font. if (label.length() > 1 && key.codes.length < 2) { paint.setTextSize(mLabelTextSize); - paint.setFakeBoldText(true); + paint.setTypeface(Typeface.DEFAULT_BOLD); } else { paint.setTextSize(mKeyTextSize); - paint.setFakeBoldText(false); + paint.setTypeface(Typeface.DEFAULT); } // Draw a drop shadow for the text paint.setShadowLayer(3f, 0, 0, 0xCC000000); @@ -878,6 +889,7 @@ public class KeyboardView extends View implements View.OnClickListener { if (mGestureDetector.onTouchEvent(me)) { showPreview(NOT_A_KEY); mHandler.removeMessages(MSG_REPEAT); + mHandler.removeMessages(MSG_LONGPRESS); return true; } @@ -907,12 +919,17 @@ public class KeyboardView extends View implements View.OnClickListener { Message msg = mHandler.obtainMessage(MSG_REPEAT); mHandler.sendMessageDelayed(msg, REPEAT_START_DELAY); } + if (mCurrentKey != NOT_A_KEY) { + Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me); + mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT); + } showPreview(keyIndex); playKeyClick(); vibrate(); break; case MotionEvent.ACTION_MOVE: + boolean continueLongPress = false; if (keyIndex != NOT_A_KEY) { if (mCurrentKey == NOT_A_KEY) { mCurrentKey = keyIndex; @@ -920,6 +937,7 @@ public class KeyboardView extends View implements View.OnClickListener { } else { if (keyIndex == mCurrentKey) { mCurrentKeyTime += eventTime - mLastMoveTime; + continueLongPress = true; } else { resetMultiTap(); mLastKey = mCurrentKey; @@ -936,11 +954,21 @@ public class KeyboardView extends View implements View.OnClickListener { mRepeatKeyIndex = NOT_A_KEY; } } + if (!continueLongPress) { + // Cancel old longpress + mHandler.removeMessages(MSG_LONGPRESS); + // Start new longpress if key has changed + if (keyIndex != NOT_A_KEY) { + Message msg = mHandler.obtainMessage(MSG_LONGPRESS, me); + mHandler.sendMessageDelayed(msg, LONGPRESS_TIMEOUT); + } + } showPreview(keyIndex); break; case MotionEvent.ACTION_UP: mHandler.removeMessages(MSG_REPEAT); + mHandler.removeMessages(MSG_LONGPRESS); if (keyIndex == mCurrentKey) { mCurrentKeyTime += eventTime - mLastMoveTime; } else { diff --git a/core/java/android/os/HandlerState.java b/core/java/android/os/HandlerState.java new file mode 100644 index 0000000..0708f7d --- /dev/null +++ b/core/java/android/os/HandlerState.java @@ -0,0 +1,33 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +/** + * {@hide} + */ +public abstract class HandlerState { + public HandlerState() { + } + + public void enter(Message message) { + } + + public abstract void processMessage(Message message); + + public void exit(Message message) { + } +} diff --git a/core/java/android/os/HandlerStateMachine.java b/core/java/android/os/HandlerStateMachine.java new file mode 100644 index 0000000..d004a25 --- /dev/null +++ b/core/java/android/os/HandlerStateMachine.java @@ -0,0 +1,290 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.os; + +import android.util.Log; +import android.util.LogPrinter; + +/** + * {@hide} + * + * Implement a state machine where each state is an object, + * HandlerState. Each HandlerState must implement processMessage + * and optionally enter/exit. When a state machine is created + * the initial state must be set. When messages are sent to + * a state machine the current state's processMessage method is + * invoked. If this is the first message for this state the + * enter method is called prior to processMessage and when + * transtionTo is invoked the state's exit method will be + * called after returning from processMessage. + * + * If a message should be handled in a different state the + * processMessage method may call deferMessage. This causes + * the message to be saved on a list until transitioning + * to a new state, at which time all of the deferred messages + * will be put on the front of the state machines queue and + * processed by the new current state's processMessage + * method. + * + * Below is an example state machine with two state's, S1 and S2. + * The initial state is S1 which defers all messages and only + * transition to S2 when message.what == TEST_WHAT_2. State S2 + * will process each messages until it receives TEST_WHAT_2 + * where it will transition back to S1: +<code> + class StateMachine1 extends HandlerStateMachine { + private static final int TEST_WHAT_1 = 1; + private static final int TEST_WHAT_2 = 2; + + StateMachine1(String name) { + super(name); + setInitialState(mS1); + } + + class S1 extends HandlerState { + @Override public void enter(Message message) { + } + + @Override public void processMessage(Message message) { + deferMessage(message); + if (message.what == TEST_WHAT_2) { + transitionTo(mS2); + } + } + + @Override public void exit(Message message) { + } + } + + class S2 extends HandlerState { + @Override public void processMessage(Message message) { + // Do some processing + if (message.what == TEST_WHAT_2) { + transtionTo(mS1); + } + } + } + + private S1 mS1 = new S1(); + private S2 mS2 = new S2(); + } +</code> + */ +public class HandlerStateMachine { + + private boolean mDbg = false; + private static final String TAG = "HandlerStateMachine"; + private String mName; + private SmHandler mHandler; + private HandlerThread mHandlerThread; + + /** + * Handle messages sent to the state machine by calling + * the current state's processMessage. It also handles + * the enter/exit calls and placing any deferred messages + * back onto the queue when transitioning to a new state. + */ + class SmHandler extends Handler { + + SmHandler(Looper looper) { + super(looper); + } + + /** + * This will dispatch the message to the + * current state's processMessage. + */ + @Override + final public void handleMessage(Message msg) { + if (mDbg) Log.d(TAG, "SmHandler.handleMessage E"); + if (mDestState != null) { + if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destation call enter"); + mCurrentState = mDestState; + mDestState = null; + mCurrentState.enter(msg); + } + if (mCurrentState != null) { + if (mDbg) Log.d(TAG, "SmHandler.handleMessage; call processMessage"); + mCurrentState.processMessage(msg); + } else { + /* Strange no state to execute */ + Log.e(TAG, "handleMessage: no current state, did you call setInitialState"); + } + + if (mDestState != null) { + if (mDbg) Log.d(TAG, "SmHandler.handleMessage; new destination call exit"); + mCurrentState.exit(msg); + + /** + * Place the messages from the deferred queue:t + * on to the Handler's message queue in the + * same order that they originally arrived. + * + * We set cur.when = 0 to circumvent the check + * that this message has already been sent. + */ + while (mDeferredMessages != null) { + Message cur = mDeferredMessages; + mDeferredMessages = mDeferredMessages.next; + cur.when = 0; + if (mDbg) Log.d(TAG, "SmHandler.handleMessage; queue deferred message what=" + + cur.what + " target=" + cur.target); + sendMessageAtFrontOfQueue(cur); + } + if (mDbg) Log.d(TAG, "SmHandler.handleMessage X"); + } + } + + public HandlerState mCurrentState; + public HandlerState mDestState; + public Message mDeferredMessages; + } + + /** + * Create an active StateMachine, one that has a + * dedicated thread/looper/queue. + */ + public HandlerStateMachine(String name) { + mName = name; + mHandlerThread = new HandlerThread(name); + mHandlerThread.start(); + mHandler = new SmHandler(mHandlerThread.getLooper()); + } + + /** + * Get a message and set Message.target = this. + */ + public final Message obtainMessage() + { + Message msg = Message.obtain(mHandler); + if (mDbg) Log.d(TAG, "StateMachine.obtainMessage() EX target=" + msg.target); + return msg; + } + + /** + * Get a message and set Message.target = this and + * Message.what = what. + */ + public final Message obtainMessage(int what) { + Message msg = Message.obtain(mHandler, what); + if (mDbg) { + Log.d(TAG, "StateMachine.obtainMessage(what) EX what=" + msg.what + + " target=" + msg.target); + } + return msg; + } + + /** + * Enqueue a message to this state machine. + */ + public final void sendMessage(Message msg) { + if (mDbg) Log.d(TAG, "StateMachine.sendMessage EX msg.what=" + msg.what); + mHandler.sendMessage(msg); + } + + /** + * Enqueue a message to this state machine after a delay. + */ + public final void sendMessageDelayed(Message msg, long delayMillis) { + if (mDbg) { + Log.d(TAG, "StateMachine.sendMessageDelayed EX msg.what=" + + msg.what + " delay=" + delayMillis); + } + mHandler.sendMessageDelayed(msg, delayMillis); + } + + /** + * Set the initial state. This must be invoked before + * and messages are sent to the state machine. + */ + public void setInitialState(HandlerState initialState) { + if (mDbg) { + Log.d(TAG, "StateMachine.setInitialState EX initialState" + + initialState.getClass().getName()); + } + mHandler.mDestState = initialState; + } + + /** + * transition to destination state. Upon returning + * from processMessage the current state's exit will + * be executed and upon the next message arriving + * destState.enter will be invoked. + */ + final public void transitionTo(HandlerState destState) { + if (mDbg) { + Log.d(TAG, "StateMachine.transitionTo EX destState" + + destState.getClass().getName()); + } + mHandler.mDestState = destState; + } + + /** + * Defer this message until next state transition. + * Upon transitioning all deferred messages will be + * placed on the queue and reprocessed in the original + * order. (i.e. The next state the oldest messages will + * be processed first) + */ + final public void deferMessage(Message msg) { + if (mDbg) { + Log.d(TAG, "StateMachine.deferMessage EX mDeferredMessages=" + + mHandler.mDeferredMessages); + } + + /* Copy the "msg" to "newMsg" as "msg" will be recycled */ + Message newMsg = obtainMessage(); + newMsg.copyFrom(msg); + + /* Place on front of queue */ + newMsg.next = mHandler.mDeferredMessages; + mHandler.mDeferredMessages = newMsg; + } + + /** + * @return the name + */ + public String getName() { + return mName; + } + + /** + * @return Handler + */ + public Handler getHandler() { + return mHandler; + } + + /** + * @return if debugging is enabled + */ + public boolean isDbg() { + return mDbg; + } + + /** + * Set debug enable/disabled. + */ + public void setDbg(boolean dbg) { + mDbg = dbg; + if (mDbg) { + mHandlerThread.getLooper().setMessageLogging(new LogPrinter(Log.VERBOSE, TAG)); + } else { + mHandlerThread.getLooper().setMessageLogging(null); + } + } +} diff --git a/core/java/android/os/IPowerManager.aidl b/core/java/android/os/IPowerManager.aidl index abc1e2f..e48f152 100644 --- a/core/java/android/os/IPowerManager.aidl +++ b/core/java/android/os/IPowerManager.aidl @@ -28,4 +28,5 @@ interface IPowerManager void setPokeLock(int pokey, IBinder lock, String tag); void setStayOnSetting(int val); long getScreenOnTime(); + void preventScreenOn(boolean prevent); } diff --git a/core/java/android/os/Power.java b/core/java/android/os/Power.java index 0794e6d..b53e227 100644 --- a/core/java/android/os/Power.java +++ b/core/java/android/os/Power.java @@ -16,6 +16,8 @@ package android.os; +import java.io.IOException; + /** * Class that provides access to some of the power management functions. * @@ -71,12 +73,12 @@ public class Power * Brightness value for dim backlight */ public static final int BRIGHTNESS_DIM = 20; - + /** * Brightness value for fully on */ public static final int BRIGHTNESS_ON = 255; - + /** * Brightness value to use when battery is low */ @@ -104,11 +106,11 @@ public class Power public static native int setScreenState(boolean on); public static native int setLastUserActivityTimeout(long ms); - + /** * Turn the device off. - * - * This method is considered deprecated in favor of + * + * This method is considered deprecated in favor of * {@link android.policy.ShutdownThread.shutdownAfterDisablingRadio()}. * * @deprecated @@ -120,7 +122,9 @@ public class Power /** * Reboot the device. * @param reason code to pass to the kernel (e.g. "recovery"), or null. + * + * @throws IOException if reboot fails for some reason (eg, lack of + * permission) */ - public static native void reboot(String reason); + public static native void reboot(String reason) throws IOException; } - diff --git a/core/java/android/preference/PreferenceGroupAdapter.java b/core/java/android/preference/PreferenceGroupAdapter.java index e2a3157..05c2952 100644 --- a/core/java/android/preference/PreferenceGroupAdapter.java +++ b/core/java/android/preference/PreferenceGroupAdapter.java @@ -133,11 +133,10 @@ class PreferenceGroupAdapter extends BaseAdapter implements OnPreferenceChangeIn final PreferenceGroup preferenceAsGroup = (PreferenceGroup) preference; if (preferenceAsGroup.isOnSameScreenAsChildren()) { flattenPreferenceGroup(preferences, preferenceAsGroup); - preference.setOnPreferenceChangeInternalListener(this); } - } else { - preference.setOnPreferenceChangeInternalListener(this); } + + preference.setOnPreferenceChangeInternalListener(this); } } diff --git a/core/java/android/provider/Browser.java b/core/java/android/provider/Browser.java index 7aaed49..76aa51d 100644 --- a/core/java/android/provider/Browser.java +++ b/core/java/android/provider/Browser.java @@ -372,15 +372,13 @@ public class Browser { SEARCHES_WHERE_CLAUSE, new String [] { search }, null); + ContentValues map = new ContentValues(); + map.put(SearchColumns.SEARCH, search); + map.put(SearchColumns.DATE, now); /* We should only get one answer that is exactly the same. */ if (c.moveToFirst()) { - ContentValues map = new ContentValues(); - map.put(BookmarkColumns.DATE, now); - cr.update(BOOKMARKS_URI, map, "_id = " + c.getInt(0), null); + cr.update(SEARCHES_URI, map, "_id = " + c.getInt(0), null); } else { - ContentValues map = new ContentValues(); - map.put(SearchColumns.SEARCH, search); - map.put(SearchColumns.DATE, now); cr.insert(SEARCHES_URI, map); } c.deactivate(); diff --git a/core/java/android/provider/Contacts.java b/core/java/android/provider/Contacts.java index 6d24ba8..085c095 100644 --- a/core/java/android/provider/Contacts.java +++ b/core/java/android/provider/Contacts.java @@ -170,12 +170,20 @@ public class Contacts { */ public interface PeopleColumns { /** - * The persons name. + * The person's name. * <P>Type: TEXT</P> */ public static final String NAME = "name"; /** + * Phonetic equivalent of the person's name, in a locale-dependent + * character set (e.g. hiragana for Japanese). + * Used for pronunciation and/or collation in some languages. + * <p>Type: TEXT</P> + */ + public static final String PHONETIC_NAME = "phonetic_name"; + + /** * The display name. If name is not null name, else if number is not null number, * else if email is not null email. * <P>Type: TEXT</P> @@ -1509,6 +1517,12 @@ public class Contacts { public static final String NAME = "name"; /** + * The extra field for the contact phonetic name. + * <P>Type: String</P> + */ + public static final String PHONETIC_NAME = "phonetic_name"; + + /** * The extra field for the contact company. * <P>Type: String</P> */ @@ -1535,7 +1549,7 @@ public class Contacts { /** * The extra field for the contact phone number type. * <P>Type: Either an integer value from {@link android.provider.Contacts.PhonesColumns PhonesColumns}, - * or a string specifying a type and label.</P> + * or a string specifying a custom label.</P> */ public static final String PHONE_TYPE = "phone_type"; @@ -1554,7 +1568,7 @@ public class Contacts { /** * The extra field for an optional second contact phone number type. * <P>Type: Either an integer value from {@link android.provider.Contacts.PhonesColumns PhonesColumns}, - * or a string specifying a type and label.</P> + * or a string specifying a custom label.</P> */ public static final String SECONDARY_PHONE_TYPE = "secondary_phone_type"; @@ -1567,7 +1581,7 @@ public class Contacts { /** * The extra field for an optional third contact phone number type. * <P>Type: Either an integer value from {@link android.provider.Contacts.PhonesColumns PhonesColumns}, - * or a string specifying a type and label.</P> + * or a string specifying a custom label.</P> */ public static final String TERTIARY_PHONE_TYPE = "tertiary_phone_type"; @@ -1580,7 +1594,7 @@ public class Contacts { /** * The extra field for the contact email type. * <P>Type: Either an integer value from {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns} - * or a string specifying a type and label.</P> + * or a string specifying a custom label.</P> */ public static final String EMAIL_TYPE = "email_type"; @@ -1599,7 +1613,7 @@ public class Contacts { /** * The extra field for an optional second contact email type. * <P>Type: Either an integer value from {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns} - * or a string specifying a type and label.</P> + * or a string specifying a custom label.</P> */ public static final String SECONDARY_EMAIL_TYPE = "secondary_email_type"; @@ -1612,7 +1626,7 @@ public class Contacts { /** * The extra field for an optional third contact email type. * <P>Type: Either an integer value from {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns} - * or a string specifying a type and label.</P> + * or a string specifying a custom label.</P> */ public static final String TERTIARY_EMAIL_TYPE = "tertiary_email_type"; @@ -1625,7 +1639,7 @@ public class Contacts { /** * The extra field for the contact postal address type. * <P>Type: Either an integer value from {@link android.provider.Contacts.ContactMethodsColumns ContactMethodsColumns} - * or a string specifying a type and label.</P> + * or a string specifying a custom label.</P> */ public static final String POSTAL_TYPE = "postal_type"; diff --git a/core/java/android/provider/MediaStore.java b/core/java/android/provider/MediaStore.java index d4b728b..7b2f18c 100644 --- a/core/java/android/provider/MediaStore.java +++ b/core/java/android/provider/MediaStore.java @@ -84,13 +84,27 @@ public final class MediaStore public static final String EXTRA_MEDIA_FOCUS = "android.intent.extra.focus"; /** - * The name of the Intent-extra used to control the orientation of a MovieView. - * This is an int property that overrides the MovieView activity's requestedOrientation. + * The name of the Intent-extra used to control the orientation of a ViewImage or a MovieView. + * This is an int property that overrides the activity's requestedOrientation. * @see android.content.pm.ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED */ public static final String EXTRA_SCREEN_ORIENTATION = "android.intent.extra.screenOrientation"; /** + * The name of the Intent-extra used to control the orientation of a ViewImage. + * This is a boolean property that overrides the activity's default fullscreen state. + * @hide + */ + public static final String EXTRA_FULL_SCREEN = "android.intent.extra.fullScreen"; + + /** + * The name of the Intent-extra used to control the orientation of a ViewImage. + * This is a boolean property that specifies whether or not to show action icons. + * @hide + */ + public static final String EXTRA_SHOW_ACTION_ICONS = "android.intent.extra.showActionIcons"; + + /** * The name of the Intent-extra used to control the onCompletion behavior of a MovieView. * This is a boolean property that specifies whether or not to finish the MovieView activity * when the movie completes playing. The default value is true, which means to automatically @@ -118,6 +132,22 @@ public final class MediaStore public final static String ACTION_IMAGE_CAPTURE = "android.media.action.IMAGE_CAPTURE"; /** + * Standard Intent action that can be sent to have the media application + * capture an video and return it. The caller may pass in an extra EXTRA_VIDEO_QUALITY + * control the video quality. + * @hide + */ + public final static String ACTION_VIDEO_CAPTURE = "android.media.action.VIDEO_CAPTURE"; + + /** + * The name of the Intent-extra used to control the quality of a recorded video. This is an + * integer property. Currently value 0 means low quality, suitable for MMS messages, and + * value 1 means high quality. In the future other quality levels may be added. + * @hide + */ + public final static String EXTRA_VIDEO_QUALITY = "android.intent.extra.videoQuality"; + + /** * Common fields for most MediaProvider tables */ @@ -1162,13 +1192,21 @@ public final class MediaStore public static final class Video { /** - * The default sort order for this table + * deprecated Replaced by DEFAULT_SORT_ORDER2 + * This variable is a mistake that is retained for backwards compatibility. + * (There is no "name" column in the Video table.) */ public static final String DEFAULT_SORT_ORDER = "name ASC"; + /** + * The default sort order for this table + * @hide + */ + public static final String DEFAULT_SORT_ORDER2 = MediaColumns.DISPLAY_NAME; + public static final Cursor query(ContentResolver cr, Uri uri, String[] projection) { - return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER); + return cr.query(uri, projection, null, null, DEFAULT_SORT_ORDER2); } public interface VideoColumns extends MediaColumns { diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index e93bbeb..91624ad 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1772,9 +1772,19 @@ public final class Settings { /** * The Logging ID (a unique 64-bit value) as a hex string. * Used as a pseudonymous identifier for logging. + * @deprecated This identifier is poorly initialized and has + * many collisions. It should not be used. */ + @Deprecated public static final String LOGGING_ID = "logging_id"; - + + /** + * The Logging ID (a unique 64-bit value) as a hex string. + * Used as a pseudonymous identifier for logging. + * @hide + */ + public static final String LOGGING_ID2 = "logging_id2"; + /** * User preference for which network(s) should be used. Only the * connectivity service should touch this. diff --git a/core/java/android/provider/Sync.java b/core/java/android/provider/Sync.java index 2086a5d..94bf807 100644 --- a/core/java/android/provider/Sync.java +++ b/core/java/android/provider/Sync.java @@ -25,7 +25,6 @@ import android.os.Handler; import java.util.Map; - /** * The Sync provider stores information used in managing the syncing of the device, * including the history and pending syncs. @@ -500,6 +499,9 @@ public final class Sync { /** controls whether or not the individual provider is synced when tickles are received */ public static final String SETTING_SYNC_PROVIDER_PREFIX = "sync_provider_"; + /** query column project */ + private static final String[] PROJECTION = { KEY, VALUE }; + /** * Convenience function for updating a single settings value as a * boolean. This will either create a new entry in the table if the @@ -521,6 +523,32 @@ public final class Sync { } /** + * Convenience function for getting a setting value as a boolean without using the + * QueryMap for light-weight setting querying. + * @param contentResolver The ContentResolver for querying the setting. + * @param name The name of the setting to query + * @param def The default value for the setting. + * @return The value of the setting. + */ + static public boolean getBoolean(ContentResolver contentResolver, + String name, boolean def) { + Cursor cursor = contentResolver.query( + CONTENT_URI, + PROJECTION, + KEY + "=?", + new String[] { name }, + null); + try { + if (cursor != null && cursor.moveToFirst()) { + return Boolean.parseBoolean(cursor.getString(1)); + } + } finally { + if (cursor != null) cursor.close(); + } + return def; + } + + /** * A convenience method to set whether or not the provider is synced when * it receives a network tickle. * diff --git a/core/java/android/server/BluetoothA2dpService.java b/core/java/android/server/BluetoothA2dpService.java index 3cbb855..ded50e4 100644 --- a/core/java/android/server/BluetoothA2dpService.java +++ b/core/java/android/server/BluetoothA2dpService.java @@ -45,7 +45,7 @@ import java.util.HashMap; import java.util.Iterator; public class BluetoothA2dpService extends IBluetoothA2dp.Stub { - private static final String TAG = "BluetoothDeviceService"; + private static final String TAG = "BluetoothA2dpService"; private static final boolean DBG = true; public static final String BLUETOOTH_A2DP_SERVICE = "bluetooth_a2dp"; @@ -143,12 +143,27 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { if (path == null) { return BluetoothError.ERROR; } - if (!connectSinkNative(path)) { + + SinkState sink = mAudioDevices.get(path); + int state = BluetoothA2dp.STATE_DISCONNECTED; + if (sink != null) { + state = sink.state; + } + switch (state) { + case BluetoothA2dp.STATE_CONNECTED: + case BluetoothA2dp.STATE_PLAYING: + case BluetoothA2dp.STATE_DISCONNECTING: return BluetoothError.ERROR; - } else { - updateState(path, BluetoothA2dp.STATE_CONNECTING); + case BluetoothA2dp.STATE_CONNECTING: return BluetoothError.SUCCESS; } + + // State is DISCONNECTED + if (!connectSinkNative(path)) { + return BluetoothError.ERROR; + } + updateState(path, BluetoothA2dp.STATE_CONNECTING); + return BluetoothError.SUCCESS; } public synchronized int disconnectSink(String address) { @@ -165,6 +180,14 @@ public class BluetoothA2dpService extends IBluetoothA2dp.Stub { if (path == null) { return BluetoothError.ERROR; } + switch (mAudioDevices.get(path).state) { + case BluetoothA2dp.STATE_DISCONNECTED: + return BluetoothError.ERROR; + case BluetoothA2dp.STATE_DISCONNECTING: + return BluetoothError.SUCCESS; + } + + // State is CONNECTING or CONNECTED or PLAYING if (!disconnectSinkNative(path)) { return BluetoothError.ERROR; } else { diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java index 6c8f554..ac4cdb9 100644 --- a/core/java/android/server/search/SearchableInfo.java +++ b/core/java/android/server/search/SearchableInfo.java @@ -31,6 +31,7 @@ import android.content.res.XmlResourceParser; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; +import android.text.InputType; import android.util.AttributeSet; import android.util.Log; import android.util.Xml; @@ -75,6 +76,7 @@ public final class SearchableInfo implements Parcelable { public boolean mQueryRewriteFromText = false; private int mIconId = 0; private int mSearchButtonText = 0; + private int mSearchInputType = 0; private String mSuggestAuthority = null; private String mSuggestPath = null; private String mSuggestSelection = null; @@ -415,6 +417,10 @@ public final class SearchableInfo implements Parcelable { mIconId = a.getResourceId(com.android.internal.R.styleable.Searchable_icon, 0); mSearchButtonText = a.getResourceId( com.android.internal.R.styleable.Searchable_searchButtonText, 0); + mSearchInputType = a.getInt(com.android.internal.R.styleable.Searchable_inputType, + InputType.TYPE_CLASS_TEXT | + InputType.TYPE_TEXT_FLAG_SEARCH | + InputType.TYPE_TEXT_VARIATION_SEARCH_STRING); setSearchModeFlags(); if (DBG_INHIBIT_SUGGESTIONS == 0) { @@ -657,6 +663,16 @@ public final class SearchableInfo implements Parcelable { } /** + * Return the input type as specified in the searchable attributes. This will default to + * InputType.TYPE_CLASS_TEXT if not specified (which is appropriate for free text input). + * + * @return the input type + */ + public int getInputType() { + return mSearchInputType; + } + + /** * Return the list of searchable activities, for use in the drop-down. */ public static ArrayList<SearchableInfo> getSearchablesList() { @@ -694,6 +710,7 @@ public final class SearchableInfo implements Parcelable { mSearchMode = in.readInt(); mIconId = in.readInt(); mSearchButtonText = in.readInt(); + mSearchInputType = in.readInt(); setSearchModeFlags(); mSuggestAuthority = in.readString(); @@ -722,6 +739,7 @@ public final class SearchableInfo implements Parcelable { dest.writeInt(mSearchMode); dest.writeInt(mIconId); dest.writeInt(mSearchButtonText); + dest.writeInt(mSearchInputType); dest.writeString(mSuggestAuthority); dest.writeString(mSuggestPath); diff --git a/core/java/android/speech/RecognizerIntent.java b/core/java/android/speech/RecognizerIntent.java new file mode 100644 index 0000000..abbf8a7 --- /dev/null +++ b/core/java/android/speech/RecognizerIntent.java @@ -0,0 +1,106 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.speech; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Intent; + +/** + * Constants for supporting speech recognition through starting an {@link Intent} + * + * @hide {pending API council review} + */ +public class RecognizerIntent { + private RecognizerIntent() { + // Not for instantiating. + } + + /** + * Starts an activity that will prompt the user for speech and sends it through a + * speech recognizer. + * + * <p>Required extras: + * <ul> + * <li>{@link #EXTRA_LANGUAGE_MODEL} + * </ul> + * + * <p>Optional extras: + * <ul> + * <li>{@link Intent#EXTRA_PROMPT} + * <li>{@link #EXTRA_LANGUAGE} + * <li>{@link #EXTRA_MAX_RESULTS} + * </ul> + * + * <p> Result extras: + * <ul> + * <li>{@link #EXTRA_RESULTS} + * </ul> + * + * <p>NOTE: There may not be any applications installed to handle this action, so you should + * make sure to catch {@link ActivityNotFoundException}. + */ + public static final String ACTION_RECOGNIZE_SPEECH = "android.speech.action.RECOGNIZE_SPEECH"; + + /** + * Informs the recognizer which speech model to prefer when performing + * {@link #ACTION_RECOGNIZE_SPEECH}. The recognizer uses this + * information to fine tune the results. This extra is required. Activities implementing + * {@link #ACTION_RECOGNIZE_SPEECH} may interpret the values as they see fit. + * + * @see #LANGUAGE_MODEL_FREE_FORM + * @see #LANGUAGE_MODEL_WEB_SEARCH + */ + public static final String EXTRA_LANGUAGE_MODEL = "language_model"; + + /** Free form speech recognition */ + public static final String LANGUAGE_MODEL_FREE_FORM = "free_form"; + /** Use a language model based on web search terms */ + public static final String LANGUAGE_MODEL_WEB_SEARCH = "web_search"; + + /** Optional text prompt to show to the user when asking them to speak. */ + public static final String EXTRA_PROMPT = "prompt"; + + /** + * Optional language override to inform the recognizer that it should expect speech in + * a language different than the one set in the {@link java.util.Locale#getDefault()}. + */ + public static final String EXTRA_LANGUAGE = "lang"; + + /** + * Optional limit on the maximum number of results to return. If omitted the recognizer + * will choose how many results to return. Must be an integer. + */ + public static final String EXTRA_MAX_RESULTS = "max_results"; + + /** Result code returned when no matches are found for the given speech */ + public static final int RESULT_NO_MATCH = Activity.RESULT_FIRST_USER; + /** Result code returned when there is a generic client error */ + public static final int RESULT_CLIENT_ERROR = Activity.RESULT_FIRST_USER + 1; + /** Result code returned when the recognition server returns an error */ + public static final int RESULT_SERVER_ERROR = Activity.RESULT_FIRST_USER + 2; + /** Result code returned when a network error was encountered */ + public static final int RESULT_NETWORK_ERROR = Activity.RESULT_FIRST_USER + 3; + /** Result code returned when an audio error was encountered */ + public static final int RESULT_AUDIO_ERROR = Activity.RESULT_FIRST_USER + 4; + + /** + * An ArrayList<String> of the potential results when performing + * {@link #ACTION_RECOGNIZE_SPEECH}. Only present when {@link Activity#RESULT_OK} is returned. + */ + public static final String EXTRA_RESULTS = "results"; +} diff --git a/core/java/android/text/InputType.java b/core/java/android/text/InputType.java index 0ffe4ac..bd86834 100644 --- a/core/java/android/text/InputType.java +++ b/core/java/android/text/InputType.java @@ -1,3 +1,19 @@ +/* + * Copyright (C) 2008 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + package android.text; import android.text.TextUtils; @@ -104,6 +120,11 @@ public interface InputType { */ public static final int TYPE_TEXT_FLAG_MULTI_LINE = 0x00020000; + /** + * Flag for {@link #TYPE_CLASS_TEXT}: flags any text being used as a search string + */ + public static final int TYPE_TEXT_FLAG_SEARCH = 0x00040000; + // ---------------------------------------------------------------------- /** @@ -139,8 +160,7 @@ public interface InputType { public static final int TYPE_TEXT_VARIATION_PERSON_NAME = 0x00000050; /** - * Variation of {@link #TYPE_CLASS_TEXT}: entering a postal mailing - * address. + * Variation of {@link #TYPE_CLASS_TEXT}: entering a postal mailing address. */ public static final int TYPE_TEXT_VARIATION_POSTAL_ADDRESS = 0x00000060; @@ -150,14 +170,12 @@ public interface InputType { public static final int TYPE_TEXT_VARIATION_PASSWORD = 0x00000070; /** - * Variation of {@link #TYPE_CLASS_TEXT}: entering a search string - * for a web search. + * Variation of {@link #TYPE_CLASS_TEXT}: entering a simple text search (e.g. web search) */ - public static final int TYPE_TEXT_VARIATION_WEB_SEARCH = 0x00000080; + public static final int TYPE_TEXT_VARIATION_SEARCH_STRING = 0x00000080; /** - * Variation of {@link #TYPE_CLASS_TEXT}: entering text inside of - * a web form. + * Variation of {@link #TYPE_CLASS_TEXT}: entering text inside of a web form. */ public static final int TYPE_TEXT_VARIATION_WEB_EDIT_TEXT = 0x00000090; diff --git a/core/java/android/text/Layout.java b/core/java/android/text/Layout.java index 346db49..95acf9d 100644 --- a/core/java/android/text/Layout.java +++ b/core/java/android/text/Layout.java @@ -1074,7 +1074,9 @@ public abstract class Layout { float h2 = getSecondaryHorizontal(point) - 0.5f; int caps = TextKeyListener.getMetaState(editingBuffer, - KeyEvent.META_SHIFT_ON); + KeyEvent.META_SHIFT_ON) | + TextKeyListener.getMetaState(editingBuffer, + TextKeyListener.META_SELECTING); int fn = TextKeyListener.getMetaState(editingBuffer, KeyEvent.META_ALT_ON); int dist = 0; diff --git a/core/java/android/text/format/DateUtils.java b/core/java/android/text/format/DateUtils.java index 48f65c6..feae6cf 100644 --- a/core/java/android/text/format/DateUtils.java +++ b/core/java/android/text/format/DateUtils.java @@ -146,26 +146,26 @@ public class DateUtils // The following FORMAT_* symbols are used for specifying the format of // dates and times in the formatDateRange method. - public static final int FORMAT_SHOW_TIME = 0x00001; - public static final int FORMAT_SHOW_WEEKDAY = 0x00002; - public static final int FORMAT_SHOW_YEAR = 0x00004; - public static final int FORMAT_NO_YEAR = 0x00008; - public static final int FORMAT_SHOW_DATE = 0x00010; - public static final int FORMAT_NO_MONTH_DAY = 0x00020; - public static final int FORMAT_12HOUR = 0x00040; - public static final int FORMAT_24HOUR = 0x00080; - public static final int FORMAT_CAP_AMPM = 0x00100; - public static final int FORMAT_NO_NOON = 0x00200; - public static final int FORMAT_CAP_NOON = 0x00400; - public static final int FORMAT_NO_MIDNIGHT = 0x00800; - public static final int FORMAT_CAP_MIDNIGHT = 0x01000; - public static final int FORMAT_UTC = 0x02000; - public static final int FORMAT_ABBREV_TIME = 0x04000; + public static final int FORMAT_SHOW_TIME = 0x00001; + public static final int FORMAT_SHOW_WEEKDAY = 0x00002; + public static final int FORMAT_SHOW_YEAR = 0x00004; + public static final int FORMAT_NO_YEAR = 0x00008; + public static final int FORMAT_SHOW_DATE = 0x00010; + public static final int FORMAT_NO_MONTH_DAY = 0x00020; + public static final int FORMAT_12HOUR = 0x00040; + public static final int FORMAT_24HOUR = 0x00080; + public static final int FORMAT_CAP_AMPM = 0x00100; + public static final int FORMAT_NO_NOON = 0x00200; + public static final int FORMAT_CAP_NOON = 0x00400; + public static final int FORMAT_NO_MIDNIGHT = 0x00800; + public static final int FORMAT_CAP_MIDNIGHT = 0x01000; + public static final int FORMAT_UTC = 0x02000; + public static final int FORMAT_ABBREV_TIME = 0x04000; public static final int FORMAT_ABBREV_WEEKDAY = 0x08000; - public static final int FORMAT_ABBREV_MONTH = 0x10000; - public static final int FORMAT_NUMERIC_DATE = 0x20000; - public static final int FORMAT_ABBREV_ALL = (FORMAT_ABBREV_TIME - | FORMAT_ABBREV_WEEKDAY | FORMAT_ABBREV_MONTH); + public static final int FORMAT_ABBREV_MONTH = 0x10000; + public static final int FORMAT_NUMERIC_DATE = 0x20000; + public static final int FORMAT_ABBREV_RELATIVE = 0x40000; + public static final int FORMAT_ABBREV_ALL = 0x80000; public static final int FORMAT_CAP_NOON_MIDNIGHT = (FORMAT_CAP_NOON | FORMAT_CAP_MIDNIGHT); public static final int FORMAT_NO_NOON_MIDNIGHT = (FORMAT_NO_NOON | FORMAT_NO_MIDNIGHT); @@ -233,18 +233,20 @@ public class DateUtils }; /** - * Request the full spelled-out name. - * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}. - * @more - * <p>e.g. "Sunday" or "January" + * Request the full spelled-out name. For use with the 'abbrev' parameter of + * {@link #getDayOfWeekString} and {@link #getMonthString}. + * + * @more <p> + * e.g. "Sunday" or "January" */ public static final int LENGTH_LONG = 10; /** - * Request an abbreviated version of the name. - * For use with the 'abbrev' parameter of {@link #getDayOfWeekString} and {@link #getMonthString}. - * @more - * <p>e.g. "Sun" or "Jan" + * Request an abbreviated version of the name. For use with the 'abbrev' + * parameter of {@link #getDayOfWeekString} and {@link #getMonthString}. + * + * @more <p> + * e.g. "Sun" or "Jan" */ public static final int LENGTH_MEDIUM = 20; @@ -364,53 +366,162 @@ public class DateUtils * 0, MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS, WEEK_IN_MILLIS */ public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution) { - Resources r = Resources.getSystem(); + int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_ABBREV_MONTH; + return getRelativeTimeSpanString(time, now, minResolution, flags); + } + /** + * Returns a string describing 'time' as a time relative to 'now'. + * <p> + * Time spans in the past are formatted like "42 minutes ago". Time spans in + * the future are formatted like "in 42 minutes". + * <p> + * Can use {@link #FORMAT_ABBREV_RELATIVE} flag to use abbreviated relative + * times, like "42 mins ago". + * + * @param time the time to describe, in milliseconds + * @param now the current time in milliseconds + * @param minResolution the minimum timespan to report. For example, a time + * 3 seconds in the past will be reported as "0 minutes ago" if + * this is set to MINUTE_IN_MILLIS. Pass one of 0, + * MINUTE_IN_MILLIS, HOUR_IN_MILLIS, DAY_IN_MILLIS, + * WEEK_IN_MILLIS + * @param flags a bit mask of formatting options, such as + * {@link #FORMAT_NUMERIC_DATE} or + * {@link #FORMAT_ABBREV_RELATIVE} + */ + public static CharSequence getRelativeTimeSpanString(long time, long now, long minResolution, + int flags) { + Resources r = Resources.getSystem(); + boolean abbrevRelative = (flags & (FORMAT_ABBREV_RELATIVE | FORMAT_ABBREV_ALL)) != 0; + boolean past = (now >= time); long duration = Math.abs(now - time); - + int resId; long count; if (duration < MINUTE_IN_MILLIS && minResolution < MINUTE_IN_MILLIS) { count = duration / SECOND_IN_MILLIS; if (past) { - resId = com.android.internal.R.plurals.num_seconds_ago; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_num_seconds_ago; + } else { + resId = com.android.internal.R.plurals.num_seconds_ago; + } } else { - resId = com.android.internal.R.plurals.in_num_seconds; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_in_num_seconds; + } else { + resId = com.android.internal.R.plurals.in_num_seconds; + } } } else if (duration < HOUR_IN_MILLIS && minResolution < HOUR_IN_MILLIS) { count = duration / MINUTE_IN_MILLIS; if (past) { - resId = com.android.internal.R.plurals.num_minutes_ago; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_num_minutes_ago; + } else { + resId = com.android.internal.R.plurals.num_minutes_ago; + } } else { - resId = com.android.internal.R.plurals.in_num_minutes; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_in_num_minutes; + } else { + resId = com.android.internal.R.plurals.in_num_minutes; + } } } else if (duration < DAY_IN_MILLIS && minResolution < DAY_IN_MILLIS) { count = duration / HOUR_IN_MILLIS; if (past) { - resId = com.android.internal.R.plurals.num_hours_ago; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_num_hours_ago; + } else { + resId = com.android.internal.R.plurals.num_hours_ago; + } } else { - resId = com.android.internal.R.plurals.in_num_hours; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_in_num_hours; + } else { + resId = com.android.internal.R.plurals.in_num_hours; + } } } else if (duration < WEEK_IN_MILLIS && minResolution < WEEK_IN_MILLIS) { count = duration / DAY_IN_MILLIS; if (past) { - resId = com.android.internal.R.plurals.num_days_ago; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_num_days_ago; + } else { + resId = com.android.internal.R.plurals.num_days_ago; + } } else { - resId = com.android.internal.R.plurals.in_num_days; + if (abbrevRelative) { + resId = com.android.internal.R.plurals.abbrev_in_num_days; + } else { + resId = com.android.internal.R.plurals.in_num_days; + } } } else { - // Longer than a week ago, so just show the date. - int flags = FORMAT_SHOW_DATE | FORMAT_SHOW_YEAR | FORMAT_ABBREV_MONTH; - // We know that we won't be showing the time, so it is safe to pass // in a null context. return formatDateRange(null, time, time, flags); } - + String format = r.getQuantityString(resId, (int) count); return String.format(format, count); } + + /** + * Return string describing the elapsed time since startTime formatted like + * "[relative time/date], [time]". + * <p> + * Example output strings for the US date format. + * <ul> + * <li>3 mins ago, 10:15 AM</li> + * <li>yesterday, 12:20 PM</li> + * <li>Dec 12, 4:12 AM</li> + * <li>11/14/2007, 8:20 AM</li> + * </ul> + * + * @param time some time in the past. + * @param minResolution the minimum elapsed time (in milliseconds) to report + * when showing relative times. For example, a time 3 seconds in + * the past will be reported as "0 minutes ago" if this is set to + * {@link #MINUTE_IN_MILLIS}. + * @param transitionResolution the elapsed time (in milliseconds) at which + * to stop reporting relative measurements. Elapsed times greater + * than this resolution will default to normal date formatting. + * For example, will transition from "6 days ago" to "Dec 12" + * when using {@link #WEEK_IN_MILLIS}. + */ + public static CharSequence getRelativeDateTimeString(Context c, long time, long minResolution, + long transitionResolution, int flags) { + Resources r = Resources.getSystem(); + + long now = System.currentTimeMillis(); + long duration = Math.abs(now - time); + + // getRelativeTimeSpanString() doesn't correctly format relative dates + // above a week or exact dates below a day, so clamp + // transitionResolution as needed. + if (transitionResolution > WEEK_IN_MILLIS) { + transitionResolution = WEEK_IN_MILLIS; + } else if (transitionResolution < DAY_IN_MILLIS) { + transitionResolution = DAY_IN_MILLIS; + } + + CharSequence timeClause = formatDateRange(c, time, time, FORMAT_SHOW_TIME); + + String result; + if (duration < transitionResolution) { + CharSequence relativeClause = getRelativeTimeSpanString(time, now, minResolution, flags); + result = r.getString(com.android.internal.R.string.relative_time, relativeClause, timeClause); + } else { + CharSequence dateClause = getRelativeTimeSpanString(c, time, false); + result = r.getString(com.android.internal.R.string.date_time, dateClause, timeClause); + } + + return result; + } /** * Returns a string describing a day relative to the current day. For example if the day is @@ -1005,8 +1116,8 @@ public class DateUtils boolean showYear = (flags & FORMAT_SHOW_YEAR) != 0; boolean noYear = (flags & FORMAT_NO_YEAR) != 0; boolean useUTC = (flags & FORMAT_UTC) != 0; - boolean abbrevWeekDay = (flags & FORMAT_ABBREV_WEEKDAY) != 0; - boolean abbrevMonth = (flags & FORMAT_ABBREV_MONTH) != 0; + boolean abbrevWeekDay = (flags & (FORMAT_ABBREV_WEEKDAY | FORMAT_ABBREV_ALL)) != 0; + boolean abbrevMonth = (flags & (FORMAT_ABBREV_MONTH | FORMAT_ABBREV_ALL)) != 0; boolean noMonthDay = (flags & FORMAT_NO_MONTH_DAY) != 0; boolean numericDate = (flags & FORMAT_NUMERIC_DATE) != 0; @@ -1087,7 +1198,7 @@ public class DateUtils startTimeFormat = HOUR_MINUTE_24; endTimeFormat = HOUR_MINUTE_24; } else { - boolean abbrevTime = (flags & FORMAT_ABBREV_TIME) != 0; + boolean abbrevTime = (flags & (FORMAT_ABBREV_TIME | FORMAT_ABBREV_ALL)) != 0; boolean capAMPM = (flags & FORMAT_CAP_AMPM) != 0; boolean noNoon = (flags & FORMAT_NO_NOON) != 0; boolean capNoon = (flags & FORMAT_CAP_NOON) != 0; @@ -1419,7 +1530,6 @@ public class DateUtils long now = System.currentTimeMillis(); long span = now - millis; - Resources res = c.getResources(); if (sNowTime == null) { sNowTime = new Time(); sThenTime = new Time(); @@ -1449,6 +1559,7 @@ public class DateUtils prepositionId = R.string.preposition_for_date; } if (withPreposition) { + Resources res = c.getResources(); result = res.getString(prepositionId, result); } return result; diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index 652413e..7457439 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -31,8 +31,10 @@ ArrowKeyMovementMethod implements MovementMethod { private boolean up(TextView widget, Spannable buffer) { - boolean cap = MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_SHIFT_ON) == 1; + boolean cap = (MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_SHIFT_ON) == 1) || + (MetaKeyKeyListener.getMetaState(buffer, + MetaKeyKeyListener.META_SELECTING) != 0); boolean alt = MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1; Layout layout = widget.getLayout(); @@ -55,8 +57,10 @@ implements MovementMethod } private boolean down(TextView widget, Spannable buffer) { - boolean cap = MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_SHIFT_ON) == 1; + boolean cap = (MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_SHIFT_ON) == 1) || + (MetaKeyKeyListener.getMetaState(buffer, + MetaKeyKeyListener.META_SELECTING) != 0); boolean alt = MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1; Layout layout = widget.getLayout(); @@ -79,8 +83,10 @@ implements MovementMethod } private boolean left(TextView widget, Spannable buffer) { - boolean cap = MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_SHIFT_ON) == 1; + boolean cap = (MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_SHIFT_ON) == 1) || + (MetaKeyKeyListener.getMetaState(buffer, + MetaKeyKeyListener.META_SELECTING) != 0); boolean alt = MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1; Layout layout = widget.getLayout(); @@ -101,8 +107,10 @@ implements MovementMethod } private boolean right(TextView widget, Spannable buffer) { - boolean cap = MetaKeyKeyListener.getMetaState(buffer, - KeyEvent.META_SHIFT_ON) == 1; + boolean cap = (MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_SHIFT_ON) == 1) || + (MetaKeyKeyListener.getMetaState(buffer, + MetaKeyKeyListener.META_SELECTING) != 0); boolean alt = MetaKeyKeyListener.getMetaState(buffer, KeyEvent.META_ALT_ON) == 1; Layout layout = widget.getLayout(); @@ -141,6 +149,13 @@ implements MovementMethod case KeyEvent.KEYCODE_DPAD_RIGHT: handled |= right(widget, buffer); break; + + case KeyEvent.KEYCODE_DPAD_CENTER: + if (MetaKeyKeyListener.getMetaState(buffer, MetaKeyKeyListener.META_SELECTING) != 0) { + if (widget.showContextMenu()) { + handled = true; + } + } } if (handled) { @@ -179,7 +194,10 @@ implements MovementMethod int line = layout.getLineForVertical(y); int off = layout.getOffsetForHorizontal(line, x); - boolean cap = (event.getMetaState() & KeyEvent.META_SHIFT_ON) != 0; + boolean cap = (MetaKeyKeyListener.getMetaState(buffer, + KeyEvent.META_SHIFT_ON) == 1) || + (MetaKeyKeyListener.getMetaState(buffer, + MetaKeyKeyListener.META_SELECTING) != 0); if (cap) { Selection.extendSelection(buffer, off); diff --git a/core/java/android/text/method/MetaKeyKeyListener.java b/core/java/android/text/method/MetaKeyKeyListener.java index f0305d9..d5a473b 100644 --- a/core/java/android/text/method/MetaKeyKeyListener.java +++ b/core/java/android/text/method/MetaKeyKeyListener.java @@ -22,7 +22,8 @@ import android.text.*; /** * This base class encapsulates the behavior for handling the meta keys - * (caps, fn, sym). Key listener that care about meta state should + * (shift and alt) and the pseudo-meta state of selecting text. + * Key listeners that care about meta state should * inherit from it; you should not instantiate this class directly in a client. */ @@ -31,13 +32,49 @@ public abstract class MetaKeyKeyListener { public static final int META_ALT_ON = KeyEvent.META_ALT_ON; public static final int META_SYM_ON = KeyEvent.META_SYM_ON; - public static final int META_CAP_LOCKED = KeyEvent.META_SHIFT_ON << 8; - public static final int META_ALT_LOCKED = KeyEvent.META_ALT_ON << 8; - public static final int META_SYM_LOCKED = KeyEvent.META_SYM_ON << 8; + private static final int LOCKED_SHIFT = 8; + + public static final int META_CAP_LOCKED = KeyEvent.META_SHIFT_ON << LOCKED_SHIFT; + public static final int META_ALT_LOCKED = KeyEvent.META_ALT_ON << LOCKED_SHIFT; + public static final int META_SYM_LOCKED = KeyEvent.META_SYM_ON << LOCKED_SHIFT; + + /** + * @hide pending API review + */ + public static final int META_SELECTING = 1 << 16; + + private static final int USED_SHIFT = 24; + + private static final long META_CAP_USED = ((long)KeyEvent.META_SHIFT_ON) << USED_SHIFT; + private static final long META_ALT_USED = ((long)KeyEvent.META_ALT_ON) << USED_SHIFT; + private static final long META_SYM_USED = ((long)KeyEvent.META_SYM_ON) << USED_SHIFT; + + private static final int PRESSED_SHIFT = 32; + + private static final long META_CAP_PRESSED = ((long)KeyEvent.META_SHIFT_ON) << PRESSED_SHIFT; + private static final long META_ALT_PRESSED = ((long)KeyEvent.META_ALT_ON) << PRESSED_SHIFT; + private static final long META_SYM_PRESSED = ((long)KeyEvent.META_SYM_ON) << PRESSED_SHIFT; + private static final int RELEASED_SHIFT = 40; + + private static final long META_CAP_RELEASED = ((long)KeyEvent.META_SHIFT_ON) << RELEASED_SHIFT; + private static final long META_ALT_RELEASED = ((long)KeyEvent.META_ALT_ON) << RELEASED_SHIFT; + private static final long META_SYM_RELEASED = ((long)KeyEvent.META_SYM_ON) << RELEASED_SHIFT; + + private static final long META_SHIFT_MASK = META_SHIFT_ON + | META_CAP_LOCKED | META_CAP_USED + | META_CAP_PRESSED | META_CAP_RELEASED; + private static final long META_ALT_MASK = META_ALT_ON + | META_ALT_LOCKED | META_ALT_USED + | META_ALT_PRESSED | META_ALT_RELEASED; + private static final long META_SYM_MASK = META_SYM_ON + | META_SYM_LOCKED | META_SYM_USED + | META_SYM_PRESSED | META_SYM_RELEASED; + private static final Object CAP = new Object(); private static final Object ALT = new Object(); private static final Object SYM = new Object(); + private static final Object SELECTING = new Object(); /** * Resets all meta state to inactive. @@ -46,6 +83,7 @@ public abstract class MetaKeyKeyListener { text.removeSpan(CAP); text.removeSpan(ALT); text.removeSpan(SYM); + text.removeSpan(SELECTING); } /** @@ -59,13 +97,14 @@ public abstract class MetaKeyKeyListener { public static final int getMetaState(CharSequence text) { return getActive(text, CAP, META_SHIFT_ON, META_CAP_LOCKED) | getActive(text, ALT, META_ALT_ON, META_ALT_LOCKED) | - getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED); + getActive(text, SYM, META_SYM_ON, META_SYM_LOCKED) | + getActive(text, SELECTING, META_SELECTING, META_SELECTING); } /** * Gets the state of a particular meta key. * - * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON + * @param meta META_SHIFT_ON, META_ALT_ON, META_SYM_ON, or META_SELECTING * @param text the buffer in which the meta key would have been pressed. * * @return 0 if inactive, 1 if active, 2 if locked. @@ -81,6 +120,9 @@ public abstract class MetaKeyKeyListener { case META_SYM_ON: return getActive(text, SYM, 1, 2); + case META_SELECTING: + return getActive(text, SELECTING, 1, 2); + default: return 0; } @@ -120,7 +162,8 @@ public abstract class MetaKeyKeyListener { * keep track of meta state in the specified text. */ public static boolean isMetaTracker(CharSequence text, Object what) { - return what == CAP || what == ALT || what == SYM; + return what == CAP || what == ALT || what == SYM || + what == SELECTING; } private static void adjust(Spannable content, Object what) { @@ -140,6 +183,7 @@ public abstract class MetaKeyKeyListener { resetLock(content, CAP); resetLock(content, ALT); resetLock(content, SYM); + resetLock(content, SELECTING); } private static void resetLock(Spannable content, Object what) { @@ -189,6 +233,23 @@ public abstract class MetaKeyKeyListener { } /** + * Start selecting text. + * @hide pending API review + */ + public static void startSelecting(View view, Spannable content) { + content.setSpan(SELECTING, 0, 0, PRESSED); + } + + /** + * Stop selecting text. This does not actually collapse the selection; + * call {@link android.text.Selection#setSelection} too. + * @hide pending API review + */ + public static void stopSelecting(View view, Spannable content) { + content.removeSpan(SELECTING); + } + + /** * Handles release of the meta keys. */ public boolean onKeyUp(View view, Editable content, int keyCode, @@ -225,6 +286,170 @@ public abstract class MetaKeyKeyListener { if ((states&META_SHIFT_ON) != 0) resetLock(content, CAP); if ((states&META_ALT_ON) != 0) resetLock(content, ALT); if ((states&META_SYM_ON) != 0) resetLock(content, SYM); + if ((states&META_SELECTING) != 0) resetLock(content, SELECTING); + } + + /** + * Call this if you are a method that ignores the locked meta state + * (arrow keys, for example) and you handle a key. + */ + public static long resetLockedMeta(long state) { + state = resetLock(state, META_SHIFT_ON, META_SHIFT_MASK); + state = resetLock(state, META_ALT_ON, META_ALT_MASK); + state = resetLock(state, META_SYM_ON, META_SYM_MASK); + return state; + } + + private static long resetLock(long state, int what, long mask) { + if ((state&(((long)what)<<LOCKED_SHIFT)) != 0) { + state &= ~mask; + } + return state; + } + + // --------------------------------------------------------------------- + // Version of API that operates on a state bit mask + // --------------------------------------------------------------------- + + /** + * Gets the state of the meta keys. + * + * @param state the current meta state bits. + * + * @return an integer in which each bit set to one represents a pressed + * or locked meta key. + */ + public static final int getMetaState(long state) { + return getActive(state, META_SHIFT_ON, META_SHIFT_ON, META_CAP_LOCKED) | + getActive(state, META_ALT_ON, META_ALT_ON, META_ALT_LOCKED) | + getActive(state, META_SYM_ON, META_SYM_ON, META_SYM_LOCKED); + } + + /** + * Gets the state of a particular meta key. + * + * @param state the current state bits. + * @param meta META_SHIFT_ON, META_ALT_ON, or META_SYM_ON + * + * @return 0 if inactive, 1 if active, 2 if locked. + */ + public static final int getMetaState(long state, int meta) { + switch (meta) { + case META_SHIFT_ON: + return getActive(state, meta, 1, 2); + + case META_ALT_ON: + return getActive(state, meta, 1, 2); + + case META_SYM_ON: + return getActive(state, meta, 1, 2); + + default: + return 0; + } + } + + private static int getActive(long state, int meta, int on, int lock) { + if ((state&(meta<<LOCKED_SHIFT)) != 0) { + return lock; + } else if ((state&meta) != 0) { + return on; + } else { + return 0; + } + } + + /** + * Call this method after you handle a keypress so that the meta + * state will be reset to unshifted (if it is not still down) + * or primed to be reset to unshifted (once it is released). Takes + * the current state, returns the new state. + */ + public static long adjustMetaAfterKeypress(long state) { + state = adjust(state, META_SHIFT_ON, META_SHIFT_MASK); + state = adjust(state, META_ALT_ON, META_ALT_MASK); + state = adjust(state, META_SYM_ON, META_SYM_MASK); + return state; + } + + private static long adjust(long state, int what, long mask) { + if ((state&(((long)what)<<PRESSED_SHIFT)) != 0) + return (state&~mask) | what | ((long)what)<<USED_SHIFT; + else if ((state&(((long)what)<<RELEASED_SHIFT)) != 0) + return state & ~mask; + return state; + } + + /** + * Handles presses of the meta keys. + */ + public static long handleKeyDown(long state, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { + return press(state, META_SHIFT_ON, META_SHIFT_MASK); + } + + if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT + || keyCode == KeyEvent.KEYCODE_NUM) { + return press(state, META_ALT_ON, META_ALT_MASK); + } + + if (keyCode == KeyEvent.KEYCODE_SYM) { + return press(state, META_SYM_ON, META_SYM_MASK); + } + + return state; + } + + private static long press(long state, int what, long mask) { + if ((state&(((long)what)<<PRESSED_SHIFT)) != 0) + ; // repeat before use + else if ((state&(((long)what)<<RELEASED_SHIFT)) != 0) + state = (state&~mask) | what | (((long)what) << LOCKED_SHIFT); + else if ((state&(((long)what)<<USED_SHIFT)) != 0) + ; // repeat after use + else if ((state&(((long)what)<<LOCKED_SHIFT)) != 0) + state = state&~mask; + else + state = state | what | (((long)what)<<PRESSED_SHIFT); + return state; + } + + /** + * Handles release of the meta keys. + */ + public static long handleKeyUp(long state, int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { + return release(state, META_SHIFT_ON, META_SHIFT_MASK); + } + + if (keyCode == KeyEvent.KEYCODE_ALT_LEFT || keyCode == KeyEvent.KEYCODE_ALT_RIGHT + || keyCode == KeyEvent.KEYCODE_NUM) { + return release(state, META_ALT_ON, META_ALT_MASK); + } + + if (keyCode == KeyEvent.KEYCODE_SYM) { + return release(state, META_SYM_ON, META_SYM_MASK); + } + + return state; + } + + private static long release(long state, int what, long mask) { + if ((state&(((long)what)<<USED_SHIFT)) != 0) + state = state&~mask; + else if ((state&(((long)what)<<PRESSED_SHIFT)) != 0) + state = state | what | (((long)what)<<RELEASED_SHIFT); + return state; + } + + public long clearMetaKeyState(long state, int which) { + if ((which&META_SHIFT_ON) != 0) + state = resetLock(state, META_SHIFT_ON, META_SHIFT_MASK); + if ((which&META_ALT_ON) != 0) + state = resetLock(state, META_ALT_ON, META_ALT_MASK); + if ((which&META_SYM_ON) != 0) + state = resetLock(state, META_SYM_ON, META_SYM_MASK); + return state; } /** diff --git a/core/java/android/text/style/BackgroundColorSpan.java b/core/java/android/text/style/BackgroundColorSpan.java index be6ef77..27eda69 100644 --- a/core/java/android/text/style/BackgroundColorSpan.java +++ b/core/java/android/text/style/BackgroundColorSpan.java @@ -18,7 +18,7 @@ package android.text.style; import android.text.TextPaint; -public class BackgroundColorSpan extends CharacterStyle { +public class BackgroundColorSpan extends CharacterStyle implements UpdateAppearance { private int mColor; diff --git a/core/java/android/text/style/CharacterStyle.java b/core/java/android/text/style/CharacterStyle.java index 7620d29..14dfddd 100644 --- a/core/java/android/text/style/CharacterStyle.java +++ b/core/java/android/text/style/CharacterStyle.java @@ -16,12 +16,12 @@ package android.text.style; -import android.graphics.Paint; import android.text.TextPaint; /** * The classes that affect character-level text formatting extend this - * class. Most also extend {@link MetricAffectingSpan}. + * class. Most extend its subclass {@link MetricAffectingSpan}, but simple + * ones may just implement {@link UpdateAppearance}. */ public abstract class CharacterStyle { public abstract void updateDrawState(TextPaint tp); diff --git a/core/java/android/text/style/ClickableSpan.java b/core/java/android/text/style/ClickableSpan.java index a232ed7..989ef54 100644 --- a/core/java/android/text/style/ClickableSpan.java +++ b/core/java/android/text/style/ClickableSpan.java @@ -25,7 +25,7 @@ import android.view.View; * text can be selected. If clicked, the {@link #onClick} method will * be called. */ -public abstract class ClickableSpan extends CharacterStyle { +public abstract class ClickableSpan extends CharacterStyle implements UpdateAppearance { /** * Performs the click action associated with this span. diff --git a/core/java/android/text/style/DynamicDrawableSpan.java b/core/java/android/text/style/DynamicDrawableSpan.java index dd89b68..89dc45b 100644 --- a/core/java/android/text/style/DynamicDrawableSpan.java +++ b/core/java/android/text/style/DynamicDrawableSpan.java @@ -17,18 +17,55 @@ package android.text.style; import android.graphics.Canvas; +import android.graphics.Color; import android.graphics.Paint; import android.graphics.Rect; +import android.graphics.Paint.Style; import android.graphics.drawable.Drawable; +import android.util.Log; import java.lang.ref.WeakReference; /** * */ -public abstract class DynamicDrawableSpan -extends ReplacementSpan -{ +public abstract class DynamicDrawableSpan extends ReplacementSpan { + private static final String TAG = "DynamicDrawableSpan"; + + /** + * A constant indicating that the bottom of this span should be aligned + * with the bottom of the surrounding text, i.e., at the same level as the + * lowest descender in the text. + */ + public static final int ALIGN_BOTTOM = 0; + + /** + * A constant indicating that the bottom of this span should be aligned + * with the baseline of the surrounding text. + */ + public static final int ALIGN_BASELINE = 1; + + protected final int mVerticalAlignment; + + public DynamicDrawableSpan() { + mVerticalAlignment = ALIGN_BOTTOM; + } + + /** + * @param verticalAlignment one of {@link #ALIGN_BOTTOM} or {@link #ALIGN_BASELINE}. + */ + protected DynamicDrawableSpan(int verticalAlignment) { + mVerticalAlignment = verticalAlignment; + } + + /** + * Returns the vertical alignment of this span, one of {@link #ALIGN_BOTTOM} or + * {@link #ALIGN_BASELINE}. + */ + public int getVerticalAlignment() { + return mVerticalAlignment; + } + /** * Your subclass must implement this method to provide the bitmap * to be drawn. The dimensions of the bitmap must be the same @@ -61,7 +98,12 @@ extends ReplacementSpan Drawable b = getCachedDrawable(); canvas.save(); - canvas.translate(x, bottom - b.getBounds().bottom); + int transY = bottom - b.getBounds().bottom; + if (mVerticalAlignment == ALIGN_BASELINE) { + transY -= paint.getFontMetricsInt().descent; + } + + canvas.translate(x, transY); b.draw(canvas); canvas.restore(); } diff --git a/core/java/android/text/style/ForegroundColorSpan.java b/core/java/android/text/style/ForegroundColorSpan.java index 5cccd9c..99b3381 100644 --- a/core/java/android/text/style/ForegroundColorSpan.java +++ b/core/java/android/text/style/ForegroundColorSpan.java @@ -16,10 +16,9 @@ package android.text.style; -import android.graphics.Paint; import android.text.TextPaint; -public class ForegroundColorSpan extends CharacterStyle { +public class ForegroundColorSpan extends CharacterStyle implements UpdateAppearance { private int mColor; diff --git a/core/java/android/text/style/ImageSpan.java b/core/java/android/text/style/ImageSpan.java index de067b1..2eebc0d 100644 --- a/core/java/android/text/style/ImageSpan.java +++ b/core/java/android/text/style/ImageSpan.java @@ -32,29 +32,73 @@ public class ImageSpan extends DynamicDrawableSpan { private int mResourceId; private Context mContext; private String mSource; - public ImageSpan(Bitmap b) { + this(b, ALIGN_BOTTOM); + } + + /** + * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or + * {@link DynamicDrawableSpan#ALIGN_BASELINE}. + */ + public ImageSpan(Bitmap b, int verticalAlignment) { + super(verticalAlignment); mDrawable = new BitmapDrawable(b); mDrawable.setBounds(0, 0, mDrawable.getIntrinsicWidth(), mDrawable.getIntrinsicHeight()); } public ImageSpan(Drawable d) { + this(d, ALIGN_BOTTOM); + } + + /** + * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or + * {@link DynamicDrawableSpan#ALIGN_BASELINE}. + */ + public ImageSpan(Drawable d, int verticalAlignment) { + super(verticalAlignment); mDrawable = d; } public ImageSpan(Drawable d, String source) { + this(d, source, ALIGN_BOTTOM); + } + + /** + * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or + * {@link DynamicDrawableSpan#ALIGN_BASELINE}. + */ + public ImageSpan(Drawable d, String source, int verticalAlignment) { + super(verticalAlignment); mDrawable = d; mSource = source; } public ImageSpan(Context context, Uri uri) { + this(context, uri, ALIGN_BOTTOM); + } + + /** + * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or + * {@link DynamicDrawableSpan#ALIGN_BASELINE}. + */ + public ImageSpan(Context context, Uri uri, int verticalAlignment) { + super(verticalAlignment); mContext = context; mContentUri = uri; } public ImageSpan(Context context, int resourceId) { + this(context, resourceId, ALIGN_BOTTOM); + } + + /** + * @param verticalAlignment one of {@link DynamicDrawableSpan#ALIGN_BOTTOM} or + * {@link DynamicDrawableSpan#ALIGN_BASELINE}. + */ + public ImageSpan(Context context, int resourceId, int verticalAlignment) { + super(verticalAlignment); mContext = context; mResourceId = resourceId; } diff --git a/core/java/android/text/style/MaskFilterSpan.java b/core/java/android/text/style/MaskFilterSpan.java index 781bcec..64ab0d8 100644 --- a/core/java/android/text/style/MaskFilterSpan.java +++ b/core/java/android/text/style/MaskFilterSpan.java @@ -16,11 +16,10 @@ package android.text.style; -import android.graphics.Paint; import android.graphics.MaskFilter; import android.text.TextPaint; -public class MaskFilterSpan extends CharacterStyle { +public class MaskFilterSpan extends CharacterStyle implements UpdateAppearance { private MaskFilter mFilter; diff --git a/core/java/android/text/style/RasterizerSpan.java b/core/java/android/text/style/RasterizerSpan.java index 193c700..75b5bcc 100644 --- a/core/java/android/text/style/RasterizerSpan.java +++ b/core/java/android/text/style/RasterizerSpan.java @@ -16,11 +16,10 @@ package android.text.style; -import android.graphics.Paint; import android.graphics.Rasterizer; import android.text.TextPaint; -public class RasterizerSpan extends CharacterStyle { +public class RasterizerSpan extends CharacterStyle implements UpdateAppearance { private Rasterizer mRasterizer; diff --git a/core/java/android/text/style/StrikethroughSpan.java b/core/java/android/text/style/StrikethroughSpan.java index 01ae38c..dd430e5 100644 --- a/core/java/android/text/style/StrikethroughSpan.java +++ b/core/java/android/text/style/StrikethroughSpan.java @@ -18,7 +18,7 @@ package android.text.style; import android.text.TextPaint; -public class StrikethroughSpan extends CharacterStyle { +public class StrikethroughSpan extends CharacterStyle implements UpdateAppearance { @Override public void updateDrawState(TextPaint ds) { diff --git a/core/java/android/text/style/UnderlineSpan.java b/core/java/android/text/style/UnderlineSpan.java index 5dce0f2..ca6f10c 100644 --- a/core/java/android/text/style/UnderlineSpan.java +++ b/core/java/android/text/style/UnderlineSpan.java @@ -18,7 +18,7 @@ package android.text.style; import android.text.TextPaint; -public class UnderlineSpan extends CharacterStyle { +public class UnderlineSpan extends CharacterStyle implements UpdateAppearance { @Override public void updateDrawState(TextPaint ds) { diff --git a/core/java/android/text/style/UpdateAppearance.java b/core/java/android/text/style/UpdateAppearance.java new file mode 100644 index 0000000..198e4fa --- /dev/null +++ b/core/java/android/text/style/UpdateAppearance.java @@ -0,0 +1,10 @@ +package android.text.style; + +/** + * The classes that affect character-level text in a way that modifies their + * appearance when one is added or removed must implement this interface. Note + * that if the class also impacts size or other metrics, it should instead + * implement {@link UpdateLayout}. + */ +public interface UpdateAppearance { +} diff --git a/core/java/android/text/style/UpdateLayout.java b/core/java/android/text/style/UpdateLayout.java index 211685a..591075e 100644 --- a/core/java/android/text/style/UpdateLayout.java +++ b/core/java/android/text/style/UpdateLayout.java @@ -18,7 +18,8 @@ package android.text.style; /** * The classes that affect character-level text formatting in a way that - * triggers a text layout update when one is added or remove must implement - * this interface. + * triggers a text layout update when one is added or removed must implement + * this interface. This interface also includes {@link UpdateAppearance} + * since such a change implicitly also impacts the appearance. */ -public interface UpdateLayout { } +public interface UpdateLayout extends UpdateAppearance { } diff --git a/core/java/android/util/PrintStreamPrinter.java b/core/java/android/util/PrintStreamPrinter.java new file mode 100644 index 0000000..1c11f15 --- /dev/null +++ b/core/java/android/util/PrintStreamPrinter.java @@ -0,0 +1,40 @@ +/* + * Copyright (C) 2006 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.util; + +import java.io.PrintStream; + +/** + * Implementation of a {@link android.util.Printer} that sends its output + * to a {@link java.io.PrintStream}. + */ +public class PrintStreamPrinter implements Printer { + private final PrintStream mPS; + + /** + * Create a new Printer that sends to a PrintWriter object. + * + * @param pw The PrintWriter where you would like output to go. + */ + public PrintStreamPrinter(PrintStream pw) { + mPS = pw; + } + + public void println(String x) { + mPS.println(x); + } +} diff --git a/core/java/android/view/Menu.java b/core/java/android/view/Menu.java index ca2140b..97825e6 100644 --- a/core/java/android/view/Menu.java +++ b/core/java/android/view/Menu.java @@ -385,14 +385,11 @@ public interface Menu { * @return The menu item. * @exception IndexOutOfBoundsException * when {@code index < 0 || >= size()} - * @hide pending API council */ public MenuItem getItem(int index); /** * Closes the menu, if open. - * - * @hide pending API council */ public void close(); diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 0d9e221..e928998 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -33,6 +33,7 @@ import android.util.Log; import java.util.ArrayList; import java.util.concurrent.locks.ReentrantLock; +import java.lang.ref.WeakReference; /** * Provides a dedicated drawing surface embedded inside of a view hierarchy. @@ -308,7 +309,7 @@ public class SurfaceView extends View { mLayout.memoryType = mRequestedType; if (mWindow == null) { - mWindow = new MyWindow(); + mWindow = new MyWindow(this); mLayout.type = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA; mLayout.gravity = Gravity.LEFT|Gravity.TOP; mSession.add(mWindow, mLayout, @@ -392,33 +393,45 @@ public class SurfaceView extends View { mNewSurfaceNeeded = true; updateWindow(false); } - - private class MyWindow extends IWindow.Stub { + + private static class MyWindow extends IWindow.Stub { + private WeakReference<SurfaceView> mSurfaceView; + + public MyWindow(SurfaceView surfaceView) { + mSurfaceView = new WeakReference<SurfaceView>(surfaceView); + } + public void resized(int w, int h, Rect coveredInsets, Rect visibleInsets, boolean reportDraw) { - if (localLOGV) Log.v( - "SurfaceView", SurfaceView.this + " got resized: w=" + - w + " h=" + h + ", cur w=" + mCurWidth + " h=" + mCurHeight); - synchronized (this) { - if (mCurWidth != w || mCurHeight != h) { - mCurWidth = w; - mCurHeight = h; - } - if (reportDraw) { - try { - mSession.finishDrawing(mWindow); - } catch (RemoteException e) { + SurfaceView surfaceView = mSurfaceView.get(); + if (surfaceView != null) { + if (localLOGV) Log.v( + "SurfaceView", surfaceView + " got resized: w=" + + w + " h=" + h + ", cur w=" + mCurWidth + " h=" + mCurHeight); + synchronized (this) { + if (mCurWidth != w || mCurHeight != h) { + mCurWidth = w; + mCurHeight = h; + } + if (reportDraw) { + try { + surfaceView.mSession.finishDrawing(surfaceView.mWindow); + } catch (RemoteException e) { + } } } } } public void dispatchKey(KeyEvent event) { - //Log.w("SurfaceView", "Unexpected key event in surface: " + event); - if (mSession != null && mSurface != null) { - try { - mSession.finishKey(mWindow); - } catch (RemoteException ex) { + SurfaceView surfaceView = mSurfaceView.get(); + if (surfaceView != null) { + //Log.w("SurfaceView", "Unexpected key event in surface: " + event); + if (surfaceView.mSession != null && surfaceView.mSurface != null) { + try { + surfaceView.mSession.finishKey(surfaceView.mWindow); + } catch (RemoteException ex) { + } } } } @@ -446,10 +459,13 @@ public class SurfaceView extends View { public void dispatchAppVisibility(boolean visible) { // The point of SurfaceView is to let the app control the surface. } - + public void dispatchGetNewSurface() { - Message msg = mHandler.obtainMessage(GET_NEW_SURFACE_MSG); - mHandler.sendMessage(msg); + SurfaceView surfaceView = mSurfaceView.get(); + if (surfaceView != null) { + Message msg = surfaceView.mHandler.obtainMessage(GET_NEW_SURFACE_MSG); + surfaceView.mHandler.sendMessage(msg); + } } public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) { diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index f948b33..1cc7b60 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -3562,7 +3562,7 @@ public class View implements Drawable.Callback, KeyEvent.Callback { * * @param outAttrs Fill in with attribute information about the connection. */ - public InputConnection createInputConnection(EditorInfo outAttrs) { + public InputConnection onCreateInputConnection(EditorInfo outAttrs) { return null; } @@ -4315,12 +4315,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback { if (mAttachInfo != null) { handler = mAttachInfo.mHandler; } else { - handler = ViewRoot.sUiThreads.get(); - if (handler == null) { - // Assume that post will succeed later - ViewRoot.sRunQueue.post(action); - return true; - } + // Assume that post will succeed later + ViewRoot.getRunQueue().post(action); + return true; } return handler.post(action); @@ -4347,12 +4344,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback { if (mAttachInfo != null) { handler = mAttachInfo.mHandler; } else { - handler = ViewRoot.sUiThreads.get(); - if (handler == null) { - // Assume that post will succeed later - ViewRoot.sRunQueue.postDelayed(action, delayMillis); - return true; - } + // Assume that post will succeed later + ViewRoot.getRunQueue().postDelayed(action, delayMillis); + return true; } return handler.postDelayed(action, delayMillis); @@ -4373,12 +4367,9 @@ public class View implements Drawable.Callback, KeyEvent.Callback { if (mAttachInfo != null) { handler = mAttachInfo.mHandler; } else { - handler = ViewRoot.sUiThreads.get(); - if (handler == null) { - // Assume that post will succeed later - ViewRoot.sRunQueue.removeCallbacks(action); - return true; - } + // Assume that post will succeed later + ViewRoot.getRunQueue().removeCallbacks(action); + return true; } handler.removeCallbacks(action); diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index db0b368..a254edb 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -34,6 +34,7 @@ import android.util.Log; import android.util.EventLog; import android.util.SparseArray; import android.view.View.MeasureSpec; +import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import android.widget.Scroller; import android.content.pm.PackageManager; @@ -94,8 +95,7 @@ public final class ViewRoot extends Handler implements ViewParent, static final Object mStaticInit = new Object(); static boolean mInitialized = false; - static final ThreadLocal<Handler> sUiThreads = new ThreadLocal<Handler>(); - static final RunQueue sRunQueue = new RunQueue(); + static final ThreadLocal<RunQueue> sRunQueues = new ThreadLocal<RunQueue>(); long mLastTrackballTime = 0; final TrackballAxis mTrackballAxisX = new TrackballAxis(); @@ -222,13 +222,7 @@ public final class ViewRoot extends Handler implements ViewParent, mFirst = true; // true for the first time the view is added mSurface = new Surface(); mAdded = false; - - Handler handler = sUiThreads.get(); - if (handler == null) { - handler = new RootHandler(); - sUiThreads.set(handler); - } - mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, handler, this); + mAttachInfo = new View.AttachInfo(sWindowSession, mWindow, this, this); } @Override @@ -617,7 +611,7 @@ public final class ViewRoot extends Handler implements ViewParent, attachInfo.mKeepScreenOn = false; viewVisibilityChanged = false; host.dispatchAttachedToWindow(attachInfo, 0); - sRunQueue.executeActions(attachInfo.mHandler); + getRunQueue().executeActions(attachInfo.mHandler); //Log.i(TAG, "Screen on initialized: " + attachInfo.mKeepScreenOn); } else { desiredWindowWidth = mWinFrame.width(); @@ -1400,10 +1394,22 @@ public final class ViewRoot extends Handler implements ViewParent, public final static int DISPATCH_APP_VISIBILITY = 1008; public final static int DISPATCH_GET_NEW_SURFACE = 1009; public final static int FINISHED_EVENT = 1010; + public final static int DISPATCH_KEY_FROM_IME = 1011; + public final static int FINISH_INPUT_CONNECTION = 1012; @Override public void handleMessage(Message msg) { switch (msg.what) { + case View.AttachInfo.INVALIDATE_MSG: + ((View) msg.obj).invalidate(); + break; + case View.AttachInfo.INVALIDATE_RECT_MSG: + int left = msg.arg1 >>> 16; + int top = msg.arg1 & 0xFFFF; + int right = msg.arg2 >>> 16; + int bottom = msg.arg2 & 0xFFFF; + ((View) msg.obj).invalidate(left, top, right, bottom); + break; case DO_TRAVERSAL: if (mProfile) { Debug.startMethodTracing("ViewRoot"); @@ -1583,6 +1589,18 @@ public final class ViewRoot extends Handler implements ViewParent, case DIE: dispatchDetachedFromWindow(); break; + case DISPATCH_KEY_FROM_IME: + if (LOCAL_LOGV) Log.v( + "ViewRoot", "Dispatching key " + + msg.obj + " from IME to " + mView); + deliverKeyEventToViewHierarchy((KeyEvent)msg.obj, false); + break; + case FINISH_INPUT_CONNECTION: { + InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + imm.reportFinishInputConnection((InputConnection)msg.obj); + } + } break; } } @@ -2614,24 +2632,6 @@ public final class ViewRoot extends Handler implements ViewParent, } } - private static final class RootHandler extends Handler { - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case View.AttachInfo.INVALIDATE_MSG: - ((View) msg.obj).invalidate(); - break; - case View.AttachInfo.INVALIDATE_RECT_MSG: - int left = msg.arg1 >>> 16; - int top = msg.arg1 & 0xFFFF; - int right = msg.arg2 >>> 16; - int bottom = msg.arg2 & 0xFFFF; - ((View) msg.obj).invalidate(left, top, right, bottom); - break; - } - } - } - private SurfaceHolder mHolder = new SurfaceHolder() { // we only need a SurfaceHolder for opengl. it would be nice // to implement everything else though, especially the callback @@ -2681,6 +2681,16 @@ public final class ViewRoot extends Handler implements ViewParent, } }; + static RunQueue getRunQueue() { + RunQueue rq = sRunQueues.get(); + if (rq != null) { + return rq; + } + rq = new RunQueue(); + sRunQueues.set(rq); + return rq; + } + /** * @hide */ diff --git a/core/java/android/view/animation/Animation.java b/core/java/android/view/animation/Animation.java index 9f3650b..d1d549c 100644 --- a/core/java/android/view/animation/Animation.java +++ b/core/java/android/view/animation/Animation.java @@ -118,7 +118,7 @@ public abstract class Animation { * Indicates whether the animation transformation should be applied after the * animation ends. */ - boolean mFillAfter = false; + boolean mFillAfter = true; /** * The time in milliseconds at which the animation must start; @@ -292,12 +292,18 @@ public abstract class Animation { } /** - * How long this animation should last. + * How long this animation should last. The duration cannot be negative. * * @param durationMillis Duration in milliseconds + * + * @throw java.lang.IllegalArgumentException if the duration is < 0 + * * @attr ref android.R.styleable#Animation_duration */ public void setDuration(long durationMillis) { + if (durationMillis < 0) { + throw new IllegalArgumentException("Animation duration cannot be negative"); + } mDuration = durationMillis; } @@ -401,7 +407,7 @@ public abstract class Animation { /** * If fillBefore is true, this animation will apply its transformation - * before the start time of the animation. Defaults to false if not set. + * before the start time of the animation. Defaults to true if not set. * Note that this applies when using an {@link * android.view.animation.AnimationSet AnimationSet} to chain * animations. The transformation is not applied before the AnimationSet @@ -416,7 +422,7 @@ public abstract class Animation { /** * If fillAfter is true, the transformation that this animation performed - * will persist when it is finished. Defaults to false if not set. + * will persist when it is finished. Defaults to true if not set. * Note that this applies when using an {@link * android.view.animation.AnimationSet AnimationSet} to chain * animations. The transformation is not applied before the AnimationSet @@ -607,12 +613,17 @@ public abstract class Animation { } final long startOffset = getStartOffset(); - float normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) / - (float) mDuration; + final long duration = mDuration; + float normalizedTime; + if (duration != 0) { + normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) / + (float) duration; + } else { + // time is a step-change with a zero duration + normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f; + } boolean expired = normalizedTime >= 1.0f; - // Pin time to 0.0 to 1.0 range - normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); mMore = !expired; if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) { @@ -623,6 +634,9 @@ public abstract class Animation { mStarted = true; } + // Pin time to 0.0 to 1.0 range + normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f); + if (mCycleFlip) { normalizedTime = 1.0f - normalizedTime; } @@ -788,16 +802,15 @@ public abstract class Animation { void onAnimationStart(Animation animation); /** - * <p>Notifies the end of the animation. This callback is invoked - * only for animation with repeat mode set to NO_REPEAT.</p> + * <p>Notifies the end of the animation. This callback is not invoked + * for animations with repeat count set to INFINITE.</p> * * @param animation The animation which reached its end. */ void onAnimationEnd(Animation animation); /** - * <p>Notifies the repetition of the animation. This callback is invoked - * only for animation with repeat mode set to RESTART or REVERSE.</p> + * <p>Notifies the repetition of the animation.</p> * * @param animation The animation which was repeated. */ diff --git a/core/java/android/view/animation/package.html b/core/java/android/view/animation/package.html index c358047..87c99bb 100755 --- a/core/java/android/view/animation/package.html +++ b/core/java/android/view/animation/package.html @@ -5,240 +5,16 @@ that you can use to create simple animations: <strong>tweened animation</strong>, in which you tell Android to perform a series of simple transformations (position, size, rotation, and so on) to the content of a - View; and <strong>frame - by frame animation</strong>, which loads a series of Drawable resources + View; and <strong>frame-by-frame animation</strong>, which loads a series of Drawable resources one after the other. Both animation types can be used in any View object to provide simple rotating timers, activity icons, and other useful UI elements. - Tweened animation is handled by this package; frame by frame animation is + Tweened animation is handled by this package (android.view.animation); frame-by-frame animation is handled by the {@link android.graphics.drawable.AnimationDrawable} class. - Animations do not have a pause method.</p> -<h2>Tweened Animation<a name="tweened"></a></h2> -<p> Android can perform simple visual transformations for you, including straight - line motion, size change, and transparency change, on the contents of a {@link - android.view.View View} object. These transformations are represented by the - following classes:</p> -<ul> - <li> {@link android.view.animation.AlphaAnimation AlphaAnimation} (transparency - changes) </li> - <li>{@link android.view.animation.RotateAnimation RotateAnimation} (rotations) </li> - <li> {@link android.view.animation.ScaleAnimation ScaleAnimation} (growing - or shrinking) </li> - <li>{@link android.view.animation.TranslateAnimation TranslateAnimation} - (position changes) </li> -</ul> -<p><em>Note: tweened animation does not provide tools to help you draw shapes.</em> Tweened - animation is the act of applying one or more of these - transformations applied to the contents of a View object. So, if you have a TextView - with text, you can move, rotate, grow, or shrink the text. If it has a background - image, the background image will also be transformed along with the text. </p> -<p>Animations are drawn in the area designated for the View at the start of the animation; - this area does not change to accommodate size or movement, so if your animation - moves or expands outside the original boundaries of your object, it will be clipped - to the size of the original canvas, even if the object's LayoutParams are - set to WRAP_CONTENT (the object will not resize to accommodate moving or expanding/shrinking - animations).</p> -<h3>Step 1: Define your animation </h3> -<p>The first step in creating a tweened animation is to define the transformations. - This can be done either in XML or in code. You define an animation by defining - the transformations that you want to occur, when they will occur, and how long - they should take to apply. Transformations - can be sequential or simultaneous—so, for example, you can have the contents - of a TextView move from left to right, and then rotate 180 degrees, or you can - have the text move and rotate simultaneously. Each transformation takes a set - of parameters specific for that transformation (starting size and ending size - for size change, starting angle and ending angle for rotation, and so on), and - also a set of common parameters (for instance, start time and duration). To make - several transformations happen simultaneously, give them the same start time; - to make them sequential, calculate the start time plus the duration of the preceding - transformation. </p> -<p>Screen coordinates are (0,0) at the upper left hand corner, and increase as you - go down and to the right. </p> -<p>Some values, such as pivotX, can be specified relative to the object itself or - relative to the parent. Be sure to use the proper format for what you want ("50" - for 50% relative to the parent, "50%" for 50% relative to itself).</p> -<p>You can determine how a transformation is applied over time by assigning an Interpolator - to it. Android includes several Interpolator subclasses that specify various - speed curves: for instance, AccelerateInterpolator tells a transformation - to start slow and speed up; DecelerateInterpolator tells it to start fast than slow - down, and so on. </p> -<p>If - you want a group of transformations to share a set of parameters (for example, - start time and duration), you can bundle them into an AnimationSet, which - defines the common parameters for all its children (and overrides any - values explicitly set by the children). Add your AnimationSet as - a child to the root AnimationSet (which serves to wrap all transformations into - the final animation). </p> -<p> Here is the XML that defines a simple animation. The object will first move - to the right, then rotate and double in size, then move up. Note the - transformation start times.</p> -<table width="100%" border="1"> - <tr> - <th scope="col">XML</th> - <th scope="col">Equivalent Java </th> - </tr> - <tr> - <td><pre><set android:shareInterpolator="true" - android:interpolator="@android:anim/accelerate_interpolator"> + </p> - <translate android:fromXDelta="0" - android:toXDelta="30" - android:duration="800" - android:fillAfter="true"/> - - <set android:duration="800" - android:pivotX="50%" - android:pivotY="50%" > +<p>For more information on creating tweened or frame-by-frame animations, read the discussion in the +<a href="{@docRoot}guide/topics/graphics/2d-graphics.html#tween-animation">2D Graphics</a> +Dev Guide.</p> - <rotate android:fromDegrees="0" - android:toDegrees="-90" - android:fillAfter="true" - android:startOffset="800"/> - - <scale android:fromXScale="1.0" - android:toXScale="2.0" - android:fromYScale="1.0" - android:toYScale="2.0" - android:startOffset="800" /> - </set> - - <translate android:toYDelta="-100" - android:fillAfter="true" - android:duration="800" - android:startOffset="1600"/> -</set></pre></td> - <td><pre>// Create root AnimationSet. -AnimationSet rootSet = new AnimationSet(true); -rootSet.setInterpolator(new AccelerateInterpolator()); -rootSet.setRepeatMode(Animation.NO_REPEAT); - -// Create and add first child, a motion animation. -TranslateAnimation trans1 = new TranslateAnimation(0, 30, 0, 0); -trans1.setStartOffset(0); -trans1.setDuration(800); -trans1.setFillAfter(true); -rootSet.addAnimation(trans1); - -// Create a rotate and a size animation. -RotateAnimation rotate = new RotateAnimation( - 0, - -90, - RotateAnimation.RELATIVE_TO_SELF, 0.5f, - RotateAnimation.RELATIVE_TO_SELF, 0.5f); - rotate.setFillAfter(true); - rotate.setDuration(800); - -ScaleAnimation scale = new ScaleAnimation( - 1, 2, 1, 2, // From x, to x, from y, to y - ScaleAnimation.RELATIVE_TO_SELF, 0.5f, - ScaleAnimation.RELATIVE_TO_SELF, 0.5f); - scale.setDuration(800); - scale.setFillAfter(true); - -// Add rotate and size animations to a new set, -// then add the set to the root set. -AnimationSet childSet = new AnimationSet(true); -childSet.setStartOffset(800); -childSet.addAnimation(rotate); -childSet.addAnimation(scale); -rootSet.addAnimation(childSet); - -// Add a final motion animation to the root set. -TranslateAnimation trans2 = new TranslateAnimation(0, 0, 0, -100); -trans2.setFillAfter(true); -trans2.setDuration(800); -trans2.setStartOffset(1600); -rootSet.addAnimation(trans2); - -// Start the animation. -animWindow.startAnimation(rootSet);</pre></td> - </tr> -</table> -<p> </p> -<p>The following diagram shows the animation drawn from this code: </p> -<p><img src="{@docRoot}images/tweening_example.png" alt="A tweened animation: move right, turn and grow, move up." ></p> -<p>The previous diagram shows a few important things. One is the animation itself, - and the other is that the animation can get cropped if it moves out of its originally - defined area. To avoid this, we could have sized the TextView to fill_parent - for its height. </p> -<p>If you define your animation in XML, save it in the res/anim/ folder as described - in <a href="{@docRoot}reference/available-resources.html#tweenedanimation">Resources</a>. That topic - also describes the XML tags and attributes you can use to specify transformations. </p> -<p>Animations - have the following common parameters (from the Animation interface). - If a group of animations share the same values, you can bundle them into an AnimationSet - so you don't have to set these values on each one individually.</p> -<table width="100%" border="1"> - <tr> - <th scope="col">Property</th> - <th scope="col">XML Attribute</th> - <th scope="col">Java Method / </th> - <th scope="col">Description</th> - </tr> - <tr> - <td>Start time </td> - <td><code>android:startOffset</code></td> - <td><code>Animation.setStartOffset()</code> (or <code>setStartTime()</code> for absolute time)</td> - <td>The start time (in milliseconds) of a transformation, where 0 is the - start time of the root animation set. </td> - </tr> - <tr> - <td>Duration</td> - <td><code>android:duration</code></td> - <td><code>Animation.setDuration()</code></td> - <td>The duration (in milliseconds) of a transformation. </td> - </tr> - <tr> - <td>Fill before </td> - <td><code>android:fillBefore</code></td> - <td><code>Animation.setFillBefore()</code></td> - <td>True if you want this transformation to be applied at time zero, regardless - of your start time value (you will probably never need this). </td> - </tr> - <tr> - <td>Fill after </td> - <td><code>android:fillAfter</code></td> - <td><code>Animation.SetFillAfter()</code></td> - <td>Whether you want the transform you apply to continue after the duration - of the transformation has expired. If false, the original value will - immediately be applied when the transformation is done. So, for example, - if you want to make a dot move down, then right in an "L" shape, if this - value is not true, at the end of the down motion the text box will immediately - jump back to the top before moving right. </td> - </tr> - <tr> - <td>Interpolator</td> - <td><code>android:interpolator</code></td> - <td><code>Animation.SetInterpolator()</code></td> - <td>Which interpolator to use. </td> - </tr> - <tr> - <td>Repeat mode </td> - <td>Cannot be set in XML </td> - <td><code>Animation.SetRepeatMode()</code></td> - <td>Whether and how the animation should repeat. </td> - </tr> -</table> -<p> </p> -<h3>Step 2: Load and start your animation </h3> -<ol> - <li>If you've created your transformation in XML, you'll need to load it in Java - by calling {@link android.view.animation.AnimationUtils#loadAnimation(android.content.Context, - int) AnimationUtils.loadAnimation()}. </li> - <li>Either start the animation immediately by calling {@link android.view.View#startAnimation(android.view.animation.Animation) - View.startAnimation()}, or if you have specified a start time in the animation - parameters, you can call - {@link android.view.View#setAnimation(android.view.animation.Animation) - View.setCurrentAnimation()}.</li> -</ol> -<p>The following code demonstrates loading and starting an animation. </p> -<pre>// Hook into the object to be animated. -TextView animWindow = (TextView)findViewById(R.id.anim); - -// Load the animation from XML (XML file is res/anim/move_animation.xml). -Animation anim = AnimationUtils.loadAnimation(AnimationSample.this, R.anim.move_animation); -anim.setRepeatMode(Animation.NO_REPEAT); - -// Play the animation. -animWindow.startAnimation(anim);</pre> </body> </html> diff --git a/core/java/android/view/inputmethod/BaseInputConnection.java b/core/java/android/view/inputmethod/BaseInputConnection.java index 4416ee5..a6ce293 100644 --- a/core/java/android/view/inputmethod/BaseInputConnection.java +++ b/core/java/android/view/inputmethod/BaseInputConnection.java @@ -57,8 +57,9 @@ public abstract class BaseInputConnection implements InputConnection { h = mIMM.mServedView.getHandler(); } } - if (h != null && mTargetView != null) { - h.post(new DispatchKey(event, mTargetView.getRootView())); + if (h != null) { + h.sendMessage(h.obtainMessage(ViewRoot.DISPATCH_KEY_FROM_IME, + event)); } } return false; @@ -81,18 +82,4 @@ public abstract class BaseInputConnection implements InputConnection { mIMM.updateStatusIcon(resId, packageName); return true; } - - static class DispatchKey implements Runnable { - KeyEvent mEvent; - View mView; - - DispatchKey(KeyEvent event, View v) { - mEvent = event; - mView = v; - } - - public void run() { - mView.dispatchKeyEvent(mEvent); - } - } -}
\ No newline at end of file +} diff --git a/core/java/android/view/inputmethod/DefaultInputMethod.java b/core/java/android/view/inputmethod/DefaultInputMethod.java index da5cab5..e92cbad 100644 --- a/core/java/android/view/inputmethod/DefaultInputMethod.java +++ b/core/java/android/view/inputmethod/DefaultInputMethod.java @@ -67,7 +67,7 @@ public class DefaultInputMethod implements InputMethod, InputMethodSession { } public void updateSelection(int oldSelStart, int oldSelEnd, - int newSelStart, int newSelEnd) { + int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { } public void updateCursor(Rect newCursor) { @@ -119,9 +119,9 @@ class SimpleInputMethod extends IInputMethod.Stub { } public void updateSelection(int oldSelStart, int oldSelEnd, - int newSelStart, int newSelEnd) { + int newSelStart, int newSelEnd, int candidatesStart, int candidatesEnd) { mSession.updateSelection(oldSelStart, oldSelEnd, - newSelStart, newSelEnd); + newSelStart, newSelEnd, candidatesStart, candidatesEnd); } public void updateCursor(Rect newCursor) { diff --git a/core/java/android/view/inputmethod/InputConnection.java b/core/java/android/view/inputmethod/InputConnection.java index 5f8ba1f..27461ff 100644 --- a/core/java/android/view/inputmethod/InputConnection.java +++ b/core/java/android/view/inputmethod/InputConnection.java @@ -26,6 +26,9 @@ import android.view.KeyEvent; * {@link InputMethod} back to the application that is receiving its input. It * is used to perform such things as reading text around the cursor, * committing text to the text box, and sending raw key events to the application. + * + * <p>Implementations of this interface should generally be done by + * subclassing {@link BaseInputConnection}. */ public interface InputConnection { /** @@ -137,6 +140,14 @@ public interface InputConnection { public boolean setComposingText(CharSequence text, int newCursorPosition); /** + * Have the text editor finish whatever composing text is currently + * active. This simple leaves the text as-is, removing any special + * composing styling or other state that was around it. The cursor + * position remains unchanged. + */ + public boolean finishComposingText(); + + /** * Commit text to the text box and set the new cursor position. * Any composing text set previously will be removed * automatically. diff --git a/core/java/android/view/inputmethod/InputConnectionWrapper.java b/core/java/android/view/inputmethod/InputConnectionWrapper.java index f150ad6..a41955c 100644 --- a/core/java/android/view/inputmethod/InputConnectionWrapper.java +++ b/core/java/android/view/inputmethod/InputConnectionWrapper.java @@ -65,6 +65,10 @@ public class InputConnectionWrapper implements InputConnection { return mBase.setComposingText(text, newCursorPosition); } + public boolean finishComposingText() { + return mBase.finishComposingText(); + } + public boolean commitText(CharSequence text, int newCursorPosition) { return mBase.commitText(text, newCursorPosition); } diff --git a/core/java/android/view/inputmethod/InputMethod.java b/core/java/android/view/inputmethod/InputMethod.java index 259e759..ad61f94 100644 --- a/core/java/android/view/inputmethod/InputMethod.java +++ b/core/java/android/view/inputmethod/InputMethod.java @@ -16,19 +16,17 @@ package android.view.inputmethod; -import android.graphics.Rect; import android.inputmethodservice.InputMethodService; -import android.os.Bundle; import android.os.IBinder; -import android.view.KeyEvent; -import android.view.MotionEvent; /** * The InputMethod interface represents an input method which can generate key * events and text, such as digital, email addresses, CJK characters, other * language characters, and etc., while handling various input events, and send - * the text back to the application that requests text input. - * + * the text back to the application that requests text input. See + * {@link InputMethodManager} for more general information about the + * architecture. + * * <p>Applications will not normally use this interface themselves, instead * relying on the standard interaction provided by * {@link android.widget.TextView} and {@link android.widget.EditText}. @@ -42,6 +40,14 @@ import android.view.MotionEvent; * {@link android.Manifest.permission#BIND_INPUT_METHOD} in order to interact * with the service; if this is not required, the system will not use that * input method, because it can not trust that it is not compromised. + * + * <p>The InputMethod interface is actually split into two parts: the interface + * here is the top-level interface to the input method, providing all + * access to it, which only the system can access (due to the BIND_INPUT_METHOD + * permission requirement). In addition its method + * {@link #createSession(android.view.inputmethod.InputMethod.SessionCallback)} + * can be called to instantate a secondary {@link InputMethodSession} interface + * which is what clients use to communicate with the input method. */ public interface InputMethod { /** diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index da82593..a9c46c3 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -17,8 +17,6 @@ package android.view.inputmethod; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.graphics.Rect; import android.os.Bundle; import android.os.Handler; @@ -31,6 +29,7 @@ import android.util.Log; import android.view.KeyEvent; import android.view.MotionEvent; import android.view.View; +import android.view.ViewRoot; import com.android.internal.view.IInputConnectionWrapper; import com.android.internal.view.IInputContext; @@ -43,9 +42,142 @@ import com.android.internal.view.InputBindResult; import java.util.List; /** - * Public interface to the global input method manager. You can retrieve - * an instance of this interface with + * Central system API to the overall input method framework (IMF) architecture, + * which arbitrates interaction between applications and the current input method. + * You can retrieve an instance of this interface with * {@link Context#getSystemService(String) Context.getSystemService()}. + * + * <p>Topics covered here: + * <ol> + * <li><a href="#ArchitectureOverview">Architecture Overview</a> + * </ol> + * + * <a name="ArchitectureOverview"></a> + * <h3>Architecture Overview</h3> + * + * <p>There are three primary parties involved in the input method + * framework (IMF) architecture:</p> + * + * <ul> + * <li> The <strong>input method manager</strong> as expressed by this class + * is the central point of the system that manages interaction between all + * other parts. It is expressed as the client-side API here which exists + * in each application context and communicates with a global system service + * that manages the interaction across all processes. + * <li> An <strong>input method (IME)</strong> implements a particular + * interaction model allowing the user to generate text. The system binds + * to the current input method that is use, causing it to be created and run, + * and tells it when to hide and show its UI. Only one IME is running at a time. + * <li> Multiple <strong>client applications</strong> arbitrate with the input + * method manager for input focus and control over the state of the IME. Only + * one such client is ever active (working with the IME) at a time. + * </ul> + * + * + * <a name="Applications"></a> + * <h3>Applications</h3> + * + * <p>In most cases, applications that are using the standard + * {@link android.widget.TextView} or its subclasses will have little they need + * to do to work well with soft input methods. The main things you need to + * be aware of are:</p> + * + * <ul> + * <li> Properly set the {@link android.R.attr#inputType} if your editable + * text views, so that the input method will have enough context to help the + * user in entering text into them. + * <li> Deal well with losing screen space when the input method is + * displayed. Ideally an application should handle its window being resized + * smaller, but it can rely on the system performing panning of the window + * if needed. You should set the {@link android.R.attr#windowSoftInputMode} + * attribute on your activity or the corresponding values on windows you + * create to help the system determine whether to pan or resize (it will + * try to determine this automatically but may get it wrong). + * <li> You can also control the preferred soft input state (open, closed, etc) + * for your window using the same {@link android.R.attr#windowSoftInputMode} + * attribute. + * </ul> + * + * <p>More finer-grained control is available through the APIs here to directly + * interact with the IMF and its IME -- either showing or hiding the input + * area, letting the user pick an input method, etc.</p> + * + * <p>For the rare people amongst us writing their own text editors, you + * will need to implement {@link android.view.View#onCreateInputConnection} + * to return a new instance of your own {@link InputConnection} interface + * allowing the IME to interact with your editor.</p> + * + * + * <a name="InputMethods"></a> + * <h3>Input Methods</h3> + * + * <p>An input method (IME) is implemented + * as a {@link android.app.Service}, typically deriving from + * {@link android.inputmethodservice.InputMethodService}. It must provide + * the core {@link InputMethod} interface, though this is normally handled by + * {@link android.inputmethodservice.InputMethodService} and implementors will + * only need to deal with the higher-level API there.</p> + * + * See the {@link android.inputmethodservice.InputMethodService} class for + * more information on implementing IMEs. + * + * + * <a name="Security"></a> + * <h3>Security</h3> + * + * <p>There are a lot of security issues associated with input methods, + * since they essentially have freedom to completely drive the UI and monitor + * everything the user enters. The Android input method framework also allows + * arbitrary third party IMEs, so care must be taken to restrict their + * selection and interactions.</p> + * + * <p>Here are some key points about the security architecture behind the + * IMF:</p> + * + * <ul> + * <li> <p>Only the system is allowed to directly access an IME's + * {@link InputMethod} interface, via the + * {@link android.Manifest.permission#BIND_INPUT_METHOD} permission. This is + * enforced in the system by not binding to an input method service that does + * not require this permission, so the system can guarantee no other untrusted + * clients are accessing the current input method outside of its control.</p> + * + * <li> <p>There may be many client processes of the IMF, but only one may + * be active at a time. The inactive clients can not interact with key + * parts of the IMF through the mechanisms described below.</p> + * + * <li> <p>Clients of an input method are only given access to its + * {@link InputMethodSession} interface. One instance of this interface is + * created for each client, and only calls from the session associated with + * the active client will be processed by the current IME. This is enforced + * by {@link android.inputmethodservice.AbstractInputMethodService} for normal + * IMEs, but must be explicitly handled by an IME that is customizing the + * raw {@link InputMethodSession} implementation.</p> + * + * <li> <p>Only the active client's {@link InputConnection} will accept + * operations. The IMF tells each client process whether it is active, and + * the framework enforces that in inactive processes calls on to the current + * InputConnection will be ignored. This ensures that the current IME can + * only deliver events and text edits to the UI that the user sees as + * being in focus.</p> + * + * <li> <p>An IME can never interact with an {@link InputConnection} while + * the screen is off. This is enforced by making all clients inactive while + * the screen is off, and prevents bad IMEs from driving the UI when the user + * can not be aware of its behavior.</p> + * + * <li> <p>A client application can ask that the system let the user pick a + * new IME, but can not programmatically switch to one itself. This avoids + * malicious applications from switching the user to their own IME, which + * remains running when the user navigates away to another application. An + * IME, on the other hand, <em>is</em> allowed to programmatically switch + * the system to another IME, since it already has full control of user + * input.</p> + * + * <li> <p>The user must explicitly enable a new IME in settings before + * they can switch to it, to confirm with the system that they know about it + * and want to make it available for use.</p> + * </ul> */ public final class InputMethodManager { static final boolean DEBUG = false; @@ -118,6 +250,8 @@ public final class InputMethodManager { Rect mCursorRect = new Rect(); int mCursorSelStart; int mCursorSelEnd; + int mCursorCandStart; + int mCursorCandEnd; // ----------------------------------------------------------- @@ -203,6 +337,10 @@ public final class InputMethodManager { return false; } + public boolean finishComposingText() { + return false; + } + public boolean showStatusIcon(String packageName, int resId) { return false; } @@ -294,6 +432,9 @@ public final class InputMethodManager { public boolean setComposingText(CharSequence text, int newCursorPosition) { return false; } + public boolean finishComposingText() { + return false; + } }; InputMethodManager(IInputMethodManager service, Looper looper) { @@ -430,23 +571,45 @@ public final class InputMethodManager { * Disconnect any existing input connection, clearing the served view. */ void finishInputLocked() { - synchronized (mH) { - if (mServedView != null) { - if (DEBUG) Log.v(TAG, "FINISH INPUT: " + mServedView); - updateStatusIcon(0, null); - - if (mCurrentTextBoxAttribute != null) { - try { - mService.finishInput(mClient); - } catch (RemoteException e) { - } + if (mServedView != null) { + if (DEBUG) Log.v(TAG, "FINISH INPUT: " + mServedView); + updateStatusIcon(0, null); + + if (mCurrentTextBoxAttribute != null) { + try { + mService.finishInput(mClient); + } catch (RemoteException e) { } - - mServedView = null; - mCompletions = null; - mServedConnecting = false; - clearConnectionLocked(); } + + if (mServedInputConnection != null) { + // We need to tell the previously served view that it is no + // longer the input target, so it can reset its state. Schedule + // this call on its window's Handler so it will be on the correct + // thread and outside of our lock. + Handler vh = mServedView.getHandler(); + if (vh != null) { + // This will result in a call to reportFinishInputConnection() + // below. + vh.sendMessage(vh.obtainMessage(ViewRoot.FINISH_INPUT_CONNECTION, + mServedInputConnection)); + } + } + + mServedView = null; + mCompletions = null; + mServedConnecting = false; + clearConnectionLocked(); + } + } + + /** + * Called from the FINISH_INPUT_CONNECTION message above. + * @hide + */ + public void reportFinishInputConnection(InputConnection ic) { + if (mServedInputConnection != ic) { + ic.finishComposingText(); } } @@ -584,7 +747,7 @@ public final class InputMethodManager { // do its stuff. // Life is good: let's hook everything up! EditorInfo tba = new EditorInfo(); - InputConnection ic = view.createInputConnection(tba); + InputConnection ic = view.onCreateInputConnection(tba); if (DEBUG) Log.v(TAG, "Starting input: tba=" + tba + " ic=" + ic); synchronized (mH) { @@ -609,6 +772,8 @@ public final class InputMethodManager { if (ic != null) { mCursorSelStart = tba.initialSelStart; mCursorSelEnd = tba.initialSelEnd; + mCursorCandStart = -1; + mCursorCandEnd = -1; mCursorRect.setEmpty(); setCurrentInputConnection(ic); } else { @@ -678,16 +843,24 @@ public final class InputMethodManager { * @hide */ public void focusOut(View view) { + InputConnection ic = null; synchronized (mH) { if (DEBUG) Log.v(TAG, "focusOut: " + view + " mServedView=" + mServedView + " winFocus=" + view.hasWindowFocus()); - if (mServedView == view && view.hasWindowFocus()) { - mLastServedView = view; - mH.removeMessages(MSG_CHECK_FOCUS); - mH.sendEmptyMessage(MSG_CHECK_FOCUS); + if (mServedView == view) { + ic = mServedInputConnection; + if (view.hasWindowFocus()) { + mLastServedView = view; + mH.removeMessages(MSG_CHECK_FOCUS); + mH.sendEmptyMessage(MSG_CHECK_FOCUS); + } } } + + if (ic != null) { + ic.finishComposingText(); + } } void checkFocus() { @@ -733,22 +906,27 @@ public final class InputMethodManager { /** * Report the current selection range. */ - public void updateSelection(View view, int selStart, int selEnd) { + public void updateSelection(View view, int selStart, int selEnd, + int candidatesStart, int candidatesEnd) { synchronized (mH) { if (mServedView != view || mCurrentTextBoxAttribute == null || mCurMethod == null) { return; } - if (mCursorSelStart != selStart || mCursorSelEnd != selEnd) { + if (mCursorSelStart != selStart || mCursorSelEnd != selEnd + || mCursorCandStart != candidatesStart + || mCursorCandEnd != candidatesEnd) { if (DEBUG) Log.d(TAG, "updateSelection"); try { if (DEBUG) Log.v(TAG, "SELECTION CHANGE: " + mCurMethod); mCurMethod.updateSelection(mCursorSelStart, mCursorSelEnd, - selStart, selEnd); + selStart, selEnd, candidatesStart, candidatesEnd); mCursorSelStart = selStart; mCursorSelEnd = selEnd; + mCursorCandStart = candidatesStart; + mCursorCandEnd = candidatesEnd; } catch (RemoteException e) { Log.w(TAG, "IME died: " + mCurId, e); } diff --git a/core/java/android/view/inputmethod/InputMethodSession.java b/core/java/android/view/inputmethod/InputMethodSession.java index 603da13..b5bbaff 100644 --- a/core/java/android/view/inputmethod/InputMethodSession.java +++ b/core/java/android/view/inputmethod/InputMethodSession.java @@ -53,9 +53,14 @@ public interface InputMethodSession { * start position. * @param newSelEnd The new text offset of the cursor selection * end position. + * @param candidatesStart The text offset of the current candidate + * text start position. + * @param candidatesEnd The text offset of the current candidate + * text end position. */ public void updateSelection(int oldSelStart, int oldSelEnd, - int newSelStart, int newSelEnd); + int newSelStart, int newSelEnd, + int candidatesStart, int candidatesEnd); /** * This method is called when cursor location of the target input field diff --git a/core/java/android/view/inputmethod/package.html b/core/java/android/view/inputmethod/package.html index 348aba6..328c7b3 100644 --- a/core/java/android/view/inputmethod/package.html +++ b/core/java/android/view/inputmethod/package.html @@ -1,7 +1,8 @@ <html> <body> Framework classes for interaction between views and input methods (such -as soft keyboards). In most cases the main classes here are not needed for +as soft keyboards). See {@link android.view.inputmethod.InputMethodManager} for +an overview. In most cases the main classes here are not needed for most applications, since they are dealt with for you by {@link android.widget.TextView}. When implementing a custom text editor, however, you will need to implement the diff --git a/core/java/android/webkit/CookieManager.java b/core/java/android/webkit/CookieManager.java index 00b17d2..5a37f04 100644 --- a/core/java/android/webkit/CookieManager.java +++ b/core/java/android/webkit/CookieManager.java @@ -172,7 +172,7 @@ public final class CookieManager { if (urlPath.startsWith(path)) { int len = path.length(); int urlLen = urlPath.length(); - if (urlLen > len) { + if (path.charAt(len-1) != PATH_DELIM && urlLen > len) { // make sure /wee doesn't match /we return urlPath.charAt(len) == PATH_DELIM; } @@ -440,29 +440,43 @@ public final class CookieManager { /** * Remove all session cookies, which are cookies without expiration date */ - public synchronized void removeSessionCookie() { - Collection<ArrayList<Cookie>> cookieList = mCookieMap.values(); - Iterator<ArrayList<Cookie>> listIter = cookieList.iterator(); - while (listIter.hasNext()) { - ArrayList<Cookie> list = listIter.next(); - Iterator<Cookie> iter = list.iterator(); - while (iter.hasNext()) { - Cookie cookie = iter.next(); - if (cookie.expires == -1) { - iter.remove(); + public void removeSessionCookie() { + final Runnable clearCache = new Runnable() { + public void run() { + synchronized(CookieManager.this) { + Collection<ArrayList<Cookie>> cookieList = mCookieMap.values(); + Iterator<ArrayList<Cookie>> listIter = cookieList.iterator(); + while (listIter.hasNext()) { + ArrayList<Cookie> list = listIter.next(); + Iterator<Cookie> iter = list.iterator(); + while (iter.hasNext()) { + Cookie cookie = iter.next(); + if (cookie.expires == -1) { + iter.remove(); + } + } + } + CookieSyncManager.getInstance().clearSessionCookies(); } } - } - CookieSyncManager.getInstance().clearSessionCookies(); + }; + new Thread(clearCache).start(); } /** * Remove all cookies */ - public synchronized void removeAllCookie() { - mCookieMap = new LinkedHashMap<String, ArrayList<Cookie>>( - MAX_DOMAIN_COUNT, 0.75f, true); - CookieSyncManager.getInstance().clearAllCookies(); + public void removeAllCookie() { + final Runnable clearCache = new Runnable() { + public void run() { + synchronized(CookieManager.this) { + mCookieMap = new LinkedHashMap<String, ArrayList<Cookie>>( + MAX_DOMAIN_COUNT, 0.75f, true); + CookieSyncManager.getInstance().clearAllCookies(); + } + } + }; + new Thread(clearCache).start(); } /** @@ -475,23 +489,30 @@ public final class CookieManager { /** * Remove all expired cookies */ - public synchronized void removeExpiredCookie() { - long now = System.currentTimeMillis(); - Collection<ArrayList<Cookie>> cookieList = mCookieMap.values(); - Iterator<ArrayList<Cookie>> listIter = cookieList.iterator(); - while (listIter.hasNext()) { - ArrayList<Cookie> list = listIter.next(); - Iterator<Cookie> iter = list.iterator(); - while (iter.hasNext()) { - Cookie cookie = iter.next(); - // expires == -1 means no expires defined. Otherwise negative - // means far future - if (cookie.expires > 0 && cookie.expires < now) { - iter.remove(); + public void removeExpiredCookie() { + final Runnable clearCache = new Runnable() { + public void run() { + synchronized(CookieManager.this) { + long now = System.currentTimeMillis(); + Collection<ArrayList<Cookie>> cookieList = mCookieMap.values(); + Iterator<ArrayList<Cookie>> listIter = cookieList.iterator(); + while (listIter.hasNext()) { + ArrayList<Cookie> list = listIter.next(); + Iterator<Cookie> iter = list.iterator(); + while (iter.hasNext()) { + Cookie cookie = iter.next(); + // expires == -1 means no expires defined. Otherwise + // negative means far future + if (cookie.expires > 0 && cookie.expires < now) { + iter.remove(); + } + } + } + CookieSyncManager.getInstance().clearExpiredCookies(now); } } - } - CookieSyncManager.getInstance().clearExpiredCookies(now); + }; + new Thread(clearCache).start(); } /** diff --git a/core/java/android/webkit/TextDialog.java b/core/java/android/webkit/TextDialog.java index 4e9370c..30b519a 100644 --- a/core/java/android/webkit/TextDialog.java +++ b/core/java/android/webkit/TextDialog.java @@ -37,6 +37,7 @@ import android.text.method.MetaKeyKeyListener; import android.text.method.MovementMethod; import android.text.method.PasswordTransformationMethod; import android.text.method.TextKeyListener; +import android.view.inputmethod.EditorInfo; import android.view.KeyCharacterMap; import android.view.KeyEvent; import android.view.MotionEvent; @@ -265,13 +266,20 @@ import android.widget.AutoCompleteTextView; if (mGotEnterDown && !down) { return true; } - // WebView check the trackballtime in onKeyDown to avoid calling native - // from both trackball and key handling. As this is called from - // TextDialog, we always want WebView to check with native. Reset - // trackballtime to ensure it. - mWebView.resetTrackballTime(); - return down ? mWebView.onKeyDown(keyCode, event) : - mWebView.onKeyUp(keyCode, event); + // if it is a navigation key, pass it to WebView + if (keyCode == KeyEvent.KEYCODE_DPAD_LEFT + || keyCode == KeyEvent.KEYCODE_DPAD_RIGHT + || keyCode == KeyEvent.KEYCODE_DPAD_UP + || keyCode == KeyEvent.KEYCODE_DPAD_DOWN) { + // WebView check the trackballtime in onKeyDown to avoid calling + // native from both trackball and key handling. As this is called + // from TextDialog, we always want WebView to check with native. + // Reset trackballtime to ensure it. + mWebView.resetTrackballTime(); + return down ? mWebView.onKeyDown(keyCode, event) : mWebView + .onKeyUp(keyCode, event); + } + return false; } /** @@ -315,31 +323,30 @@ import android.widget.AutoCompleteTextView; updateCachedTextfield(); return; } - // In this case, replace before with all but the last character of the - // new text. - if (count > 1) { - String replace = s.subSequence(start, start + count - 1).toString(); + // Find the last character being replaced. If it can be represented by + // events, we will pass them to native (after replacing the beginning + // of the changed text), so we can see javascript events. + // Otherwise, replace the text being changed (including the last + // character) in the textfield. + TextUtils.getChars(s, start + count - 1, start + count, mCharacter, 0); + KeyCharacterMap kmap = + KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); + KeyEvent[] events = kmap.getEvents(mCharacter); + boolean cannotUseKeyEvents = null == events; + int charactersFromKeyEvents = cannotUseKeyEvents ? 0 : 1; + if (count > 1 || cannotUseKeyEvents) { + String replace = s.subSequence(start, + start + count - charactersFromKeyEvents).toString(); mWebView.replaceTextfieldText(start, start + before, replace, - start + count - 1, start + count - 1); + start + count - charactersFromKeyEvents, + start + count - charactersFromKeyEvents); } else { // This corrects the selection which may have been affected by the // trackball or auto-correct. mWebView.setSelection(start, start + before); } - // Whether the text to be added is only one character, or we already - // added all but the last character, we now figure out the DOM events - // for the last character, and pass them down. - TextUtils.getChars(s, start + count - 1, start + count, mCharacter, 0); - // We only care about the events that translate directly into - // characters. Should we be using KeyCharacterMap.BUILT_IN_KEYBOARD? - // The comment makes it sound like it may not be directly related to - // the keys. However, KeyCharacterMap.ALPHA says it has "maybe some - // numbers." Not sure if that will have the numbers we may need. - KeyCharacterMap kmap = - KeyCharacterMap.load(KeyCharacterMap.BUILT_IN_KEYBOARD); - KeyEvent[] events = kmap.getEvents(mCharacter); updateCachedTextfield(); - if (null == events) { + if (cannotUseKeyEvents) { return; } int length = events.length; @@ -433,6 +440,8 @@ import android.widget.AutoCompleteTextView; method = null; } setTransformationMethod(method); + setInputType(inPassword ? EditorInfo.TYPE_TEXT_VARIATION_PASSWORD : + EditorInfo.TYPE_CLASS_TEXT); } /* package */ void setMaxLength(int maxLength) { diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index 7467b83..bd910b5 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -2841,11 +2841,6 @@ public class WebView extends AbsoluteLayout // a center key. Does not affect long press with the trackball/touch. private boolean mGotEnterDown = false; - // Enable copy/paste with trackball here. - // This should be left disabled until the framework can guarantee - // delivering matching key-up and key-down events for the shift key - private static final boolean ENABLE_COPY_PASTE = true; - @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (LOGV_ENABLED) { @@ -2876,7 +2871,7 @@ public class WebView extends AbsoluteLayout return false; } - if (ENABLE_COPY_PASTE && mShiftIsPressed == false + if (mShiftIsPressed == false && nativeFocusNodeWantsKeyEvents() == false && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) { mExtendSelection = false; @@ -2911,10 +2906,6 @@ public class WebView extends AbsoluteLayout mPrivateHandler.sendMessageDelayed(mPrivateHandler .obtainMessage(LONG_PRESS_ENTER), LONG_PRESS_TIMEOUT); nativeRecordButtons(true, true); - // FIXME, currently in webcore keydown it doesn't do anything. - // In keyup, it calls both keydown and keyup, we should fix it. - mWebViewCore.sendMessage(EventHub.KEY_DOWN, keyCode, - EventHub.KEYEVENT_UNHANDLED_TYPE, event); return true; } // Bubble up the key event as WebView doesn't handle it @@ -2949,15 +2940,10 @@ public class WebView extends AbsoluteLayout } } - if (nativeFocusNodeWantsKeyEvents()) { - mWebViewCore.sendMessage(EventHub.KEY_DOWN, keyCode, - EventHub.KEYEVENT_FOCUS_NODE_TYPE, event); - // return true as DOM handles the key - return true; - } else if (false) { // reserved to check the meta tag + // TODO: should we pass all the keys to DOM or check the meta tag + if (nativeFocusNodeWantsKeyEvents() || true) { // pass the key to DOM - mWebViewCore.sendMessage(EventHub.KEY_DOWN, keyCode, - EventHub.KEYEVENT_UNHANDLED_TYPE, event); + mWebViewCore.sendMessage(EventHub.KEY_DOWN, event); // return true as DOM handles the key return true; } @@ -3008,8 +2994,8 @@ public class WebView extends AbsoluteLayout return false; } - if (ENABLE_COPY_PASTE && (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT - || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT)) { + if (keyCode == KeyEvent.KEYCODE_SHIFT_LEFT + || keyCode == KeyEvent.KEYCODE_SHIFT_RIGHT) { if (commitCopy()) { return true; } @@ -3050,25 +3036,18 @@ public class WebView extends AbsoluteLayout Rect visibleRect = sendOurVisibleRect(); // Note that sendOurVisibleRect calls viewToContent, so the // coordinates should be in content coordinates. - boolean nodeOnScreen = false; - boolean isTextField = false; - boolean isTextArea = false; - FocusNode node = null; if (nativeUpdateFocusNode()) { - node = mFocusNode; - isTextField = node.mIsTextField; - isTextArea = node.mIsTextArea; - nodeOnScreen = Rect.intersects(node.mBounds, visibleRect); - } - if (nodeOnScreen && !isTextField && !isTextArea) { - nativeSetFollowedLink(true); - mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS, - EventHub.BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, 0, - new WebViewCore.FocusData(mFocusData)); - playSoundEffect(SoundEffectConstants.CLICK); - if (!mCallbackProxy.uiOverrideUrlLoading(node.mText)) { - mWebViewCore.sendMessage(EventHub.KEY_UP, keyCode, - EventHub.KEYEVENT_UNHANDLED_TYPE, event); + if (Rect.intersects(mFocusNode.mBounds, visibleRect)) { + nativeSetFollowedLink(true); + mWebViewCore.sendMessage(EventHub.SET_FINAL_FOCUS, + EventHub.BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP, 0, + new WebViewCore.FocusData(mFocusData)); + playSoundEffect(SoundEffectConstants.CLICK); + if (!mCallbackProxy.uiOverrideUrlLoading(mFocusNode.mText)) { + // use CLICK instead of KEY_DOWN/KEY_UP so that we can + // trigger mouse click events + mWebViewCore.sendMessage(EventHub.CLICK); + } } return true; } @@ -3076,15 +3055,10 @@ public class WebView extends AbsoluteLayout return false; } - if (nativeFocusNodeWantsKeyEvents()) { - mWebViewCore.sendMessage(EventHub.KEY_UP, keyCode, - EventHub.KEYEVENT_FOCUS_NODE_TYPE, event); - // return true as DOM handles the key - return true; - } else if (false) { // reserved to check the meta tag + // TODO: should we pass all the keys to DOM or check the meta tag + if (nativeFocusNodeWantsKeyEvents() || true) { // pass the key to DOM - mWebViewCore.sendMessage(EventHub.KEY_UP, keyCode, - EventHub.KEYEVENT_UNHANDLED_TYPE, event); + mWebViewCore.sendMessage(EventHub.KEY_UP, event); // return true as DOM handles the key return true; } @@ -3637,12 +3611,7 @@ public class WebView extends AbsoluteLayout private long mTrackballUpTime = 0; private long mLastFocusTime = 0; private Rect mLastFocusBounds; - - // Used to determine that the trackball is down AND that it has not - // been moved while down, whereas mTrackballDown is true until we - // receive an ACTION_UP - private boolean mTrackTrackball = false; - + // Set by default; BrowserActivity clears to interpret trackball data // directly for movement. Currently, the framework only passes // arrow key events, not trackball events, from one child to the next @@ -3666,7 +3635,6 @@ public class WebView extends AbsoluteLayout } if (ev.getAction() == MotionEvent.ACTION_DOWN) { mPrivateHandler.removeMessages(SWITCH_TO_ENTER); - mTrackTrackball = true; mTrackballDown = true; if (mNativeClass != 0) { nativeRecordButtons(true, true); @@ -3681,12 +3649,10 @@ public class WebView extends AbsoluteLayout + " mLastFocusTime=" + mLastFocusTime); } return false; // let common code in onKeyDown at it - } else if (mTrackTrackball) { - // LONG_PRESS_ENTER is set in common onKeyDown - mPrivateHandler.removeMessages(LONG_PRESS_ENTER); - mTrackTrackball = false; } if (ev.getAction() == MotionEvent.ACTION_UP) { + // LONG_PRESS_ENTER is set in common onKeyDown + mPrivateHandler.removeMessages(LONG_PRESS_ENTER); mTrackballDown = false; mTrackballUpTime = time; if (mShiftIsPressed) { @@ -4511,7 +4477,6 @@ public class WebView extends AbsoluteLayout // as this is shared by keydown and trackballdown, reset all // the states mGotEnterDown = false; - mTrackTrackball = false; mTrackballDown = false; // LONG_PRESS_ENTER is sent as a delayed message. If we // switch to windows overview, the WebView will be diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 9e413f9..323b44d 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -25,7 +25,6 @@ import android.graphics.Picture; import android.graphics.Point; import android.graphics.Rect; import android.graphics.Region; -import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -300,14 +299,10 @@ final class WebViewCore { */ private native void nativeSplitContent(); - // these must be kept lock-step with the KeyState enum in WebViewCore.h - static private final int KEY_ACTION_DOWN = 0; - static private final int KEY_ACTION_UP = 1; + private native boolean nativeKey(int keyCode, int unichar, + int repeatCount, boolean isShift, boolean isAlt, boolean isDown); - private native boolean nativeSendKeyToFocusNode(int keyCode, int unichar, - int repeatCount, boolean isShift, boolean isAlt, int keyAction); - - private native boolean nativeKeyUp(int keycode, int keyvalue); + private native boolean nativeClick(); private native void nativeSendListBoxChoices(boolean[] choices, int size); @@ -527,6 +522,7 @@ final class WebViewCore { static final int PASS_TO_JS = 115; static final int SET_GLOBAL_BOUNDS = 116; static final int UPDATE_CACHE_AND_TEXT_ENTRY = 117; + static final int CLICK = 118; static final int DOC_HAS_IMAGES = 120; static final int SET_SNAP_ANCHOR = 121; static final int DELETE_SELECTION = 122; @@ -573,19 +569,6 @@ final class WebViewCore { static final int NO_FOCUS_CHANGE_BLOCK = 0; static final int BLOCK_FOCUS_CHANGE_UNTIL_KEY_UP = 1; - /* The KEY_DOWN and KEY_UP messages pass the keyCode in arg1, and a - "type" in arg2. These are the types, and they describe what the - circumstances were that prompted the UI thread to send the keyevent - to webkit. - - FOCUS_NODE - the currently focused node says it wants key events - (e.g. plugins) - UNHANDLED - the UI side did not handle the key, so we give webkit - a shot at it. - */ - static final int KEYEVENT_FOCUS_NODE_TYPE = 0; - static final int KEYEVENT_UNHANDLED_TYPE = 1; - // Private handler for WebCore messages. private Handler mHandler; // Message queue for containing messages before the WebCore thread is @@ -680,11 +663,15 @@ final class WebViewCore { break; case KEY_DOWN: - keyDown(msg.arg1, msg.arg2, (KeyEvent) msg.obj); + key((KeyEvent) msg.obj, true); break; case KEY_UP: - keyUp(msg.arg1, msg.arg2, (KeyEvent) msg.obj); + key((KeyEvent) msg.obj, false); + break; + + case CLICK: + nativeClick(); break; case VIEW_SIZE_CHANGED: @@ -1130,49 +1117,19 @@ final class WebViewCore { mBrowserFrame.loadUrl(url); } - private void keyDown(int code, int target, KeyEvent event) { + private void key(KeyEvent evt, boolean isDown) { if (LOGV_ENABLED) { - Log.v(LOGTAG, "CORE keyDown at " + System.currentTimeMillis() - + ", " + event); + Log.v(LOGTAG, "CORE key at " + System.currentTimeMillis() + ", " + + evt); } - switch (target) { - case EventHub.KEYEVENT_UNHANDLED_TYPE: - break; - case EventHub.KEYEVENT_FOCUS_NODE_TYPE: - if (nativeSendKeyToFocusNode(code, event.getUnicodeChar(), - event.getRepeatCount(), - event.isShiftPressed(), - event.isAltPressed(), - KEY_ACTION_DOWN)) { - return; - } - break; + if (!nativeKey(evt.getKeyCode(), evt.getUnicodeChar(), + evt.getRepeatCount(), evt.isShiftPressed(), evt.isAltPressed(), + isDown)) { + // bubble up the event handling + mCallbackProxy.onUnhandledKeyEvent(evt); } - // If we get here, no one handled it, so call our proxy - mCallbackProxy.onUnhandledKeyEvent(event); } - private void keyUp(int code, int target, KeyEvent event) { - if (LOGV_ENABLED) { - Log.v(LOGTAG, "CORE keyUp at " + System.currentTimeMillis() - + ", " + event); - } - switch (target) { - case EventHub.KEYEVENT_UNHANDLED_TYPE: - if (!nativeKeyUp(code, event.getUnicodeChar())) { - mCallbackProxy.onUnhandledKeyEvent(event); - } - break; - case EventHub.KEYEVENT_FOCUS_NODE_TYPE: - nativeSendKeyToFocusNode(code, event.getUnicodeChar(), - event.getRepeatCount(), - event.isShiftPressed(), - event.isAltPressed(), - KEY_ACTION_UP); - break; - } - } - // These values are used to avoid requesting a layout based on old values private int mCurrentViewWidth = 0; private int mCurrentViewHeight = 0; diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index 1440522..c22023c 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -896,13 +896,16 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te public void setFilterText(String filterText) { if (mTextFilterEnabled && filterText != null && filterText.length() > 0) { createTextFilter(false); - // This is going to call our listener onTextChanged, but we are - // not ready to bring up a window yet + // This is going to call our listener onTextChanged, but we might not + // be ready to bring up a window yet mTextFilter.setText(filterText); mTextFilter.setSelection(filterText.length()); if (mAdapter instanceof Filterable) { - Filter f = ((Filterable) mAdapter).getFilter(); - f.filter(filterText); + // if mPopup is non-null, then onTextChanged will do the filtering + if (mPopup == null) { + Filter f = ((Filterable) mAdapter).getFilter(); + f.filter(filterText); + } // Set filtered to true so we will display the filter window when our main // window is ready mFiltered = true; @@ -1361,7 +1364,12 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te Rect selectorRect = mSelectorRect; if (selector != null && (isFocused() || touchModeDrawsInPressedState()) && selectorRect != null && !selectorRect.isEmpty()) { + + final View v = getChildAt(mSelectedPosition - mFirstPosition); + + if (v != null) v.setPressed(true); setPressed(true); + final boolean longClickable = isLongClickable(); Drawable d = selector.getCurrent(); if (d != null && d instanceof TransitionDrawable) { @@ -1641,9 +1649,10 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te case KeyEvent.KEYCODE_ENTER: if (isPressed() && mSelectedPosition >= 0 && mAdapter != null && mSelectedPosition < mAdapter.getCount()) { - final int index = mSelectedPosition - mFirstPosition; - performItemClick(getChildAt(index), mSelectedPosition, mSelectedRowId); + final View view = getChildAt(mSelectedPosition - mFirstPosition); + performItemClick(view, mSelectedPosition, mSelectedRowId); setPressed(false); + if (view != null) view.setPressed(false); return true; } } @@ -1763,7 +1772,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te handler.removeCallbacks(mPendingCheckForLongPress); } setPressed(false); - View motionView = this.getChildAt(mMotionPosition - mFirstPosition); + View motionView = getChildAt(mMotionPosition - mFirstPosition); if (motionView != null) { motionView.setPressed(false); } diff --git a/core/java/android/widget/AbsSeekBar.java b/core/java/android/widget/AbsSeekBar.java index 65ca885..b046a6b 100644 --- a/core/java/android/widget/AbsSeekBar.java +++ b/core/java/android/widget/AbsSeekBar.java @@ -22,6 +22,7 @@ import android.graphics.Canvas; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.util.AttributeSet; +import android.view.KeyEvent; import android.view.MotionEvent; public abstract class AbsSeekBar extends ProgressBar { @@ -54,7 +55,6 @@ public abstract class AbsSeekBar extends ProgressBar { public AbsSeekBar(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); - TypedArray a = context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.SeekBar, defStyle, 0); Drawable thumb = a.getDrawable(com.android.internal.R.styleable.SeekBar_thumb); @@ -114,10 +114,15 @@ public abstract class AbsSeekBar extends ProgressBar { if (progressDrawable != null) { progressDrawable.setAlpha(isEnabled() ? NO_ALPHA : (int) (NO_ALPHA * mDisabledAlpha)); } + + if (mThumb != null && mThumb.isStateful()) { + int[] state = getDrawableState(); + mThumb.setState(state); + } } @Override - void onProgressRefresh(float scale, boolean fromTouch) { + void onProgressRefresh(float scale, boolean fromUser) { Drawable thumb = mThumb; if (thumb != null) { setThumbPos(getWidth(), getHeight(), thumb, scale, Integer.MIN_VALUE); @@ -236,6 +241,7 @@ public abstract class AbsSeekBar extends ProgressBar { switch (event.getAction()) { case MotionEvent.ACTION_DOWN: + setPressed(true); onStartTrackingTouch(); trackTouchEvent(event); break; @@ -248,10 +254,12 @@ public abstract class AbsSeekBar extends ProgressBar { case MotionEvent.ACTION_UP: trackTouchEvent(event); onStopTrackingTouch(); + setPressed(false); break; case MotionEvent.ACTION_CANCEL: onStopTrackingTouch(); + setPressed(false); break; } return true; @@ -306,4 +314,23 @@ public abstract class AbsSeekBar extends ProgressBar { void onStopTrackingTouch() { } + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + int progress = getProgress(); + + switch (keyCode) { + case KeyEvent.KEYCODE_DPAD_LEFT: + if (progress <= 0) break; + setProgress(progress - 1, true); + return true; + + case KeyEvent.KEYCODE_DPAD_RIGHT: + if (progress >= getMax()) break; + setProgress(progress + 1, true); + return true; + } + + return super.onKeyDown(keyCode, event); + } + } diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java index d8fa603..1591791 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -336,7 +336,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe @Override public boolean onKeyUp(int keyCode, KeyEvent event) { - if (isPopupShowing()) { + if (isPopupShowing() && mDropDownList.getSelectedItemPosition() >= 0) { boolean consumed = mDropDownList.onKeyUp(keyCode, event); if (consumed) { switch (keyCode) { @@ -359,13 +359,20 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe if (isPopupShowing()) { // the key events are forwarded to the list in the drop down view // note that ListView handles space but we don't want that to happen - if (keyCode != KeyEvent.KEYCODE_SPACE) { + // also if selection is not currently in the drop down, then don't + // let center or enter presses go there since that would cause it + // to select one of its items + if (keyCode != KeyEvent.KEYCODE_SPACE + && (mDropDownList.getSelectedItemPosition() >= 0 + || (keyCode != KeyEvent.KEYCODE_ENTER + && keyCode != KeyEvent.KEYCODE_DPAD_CENTER))) { int curIndex = mDropDownList.getSelectedItemPosition(); boolean consumed; if (keyCode == KeyEvent.KEYCODE_DPAD_UP && curIndex <= 0) { // When the selection is at the top, we block the key // event to prevent focus from moving. mDropDownList.hideSelector(); + mDropDownList.requestLayout(); mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); mPopup.update(); return true; @@ -548,17 +555,21 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe private void performCompletion(View selectedView, int position, long id) { if (isPopupShowing()) { Object selectedItem; - if (position == -1) { + if (position < 0) { selectedItem = mDropDownList.getSelectedItem(); } else { selectedItem = mAdapter.getItem(position); } + if (selectedItem == null) { + Log.w(TAG, "performCompletion: no selected item"); + return; + } replaceText(convertSelectionToString(selectedItem)); if (mItemClickListener != null) { final DropDownListView list = mDropDownList; - if (selectedView == null || position == -1) { + if (selectedView == null || position < 0) { selectedView = list.getSelectedView(); position = list.getSelectedItemPosition(); id = list.getSelectedItemId(); @@ -665,8 +676,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe mPopup.setOutsideTouchable(true); mPopup.setTouchInterceptor(new PopupTouchIntercepter()); mPopup.showAsDropDown(this, mDropDownHorizontalOffset, mDropDownVerticalOffset); + mDropDownList.setSelection(ListView.INVALID_POSITION); mDropDownList.hideSelector(); - mDropDownList.setSelection(0); mDropDownList.requestFocus(); post(mHideSelector); } @@ -828,6 +839,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe public void run() { if (mDropDownList != null) { mDropDownList.hideSelector(); + mDropDownList.requestLayout(); } } } diff --git a/core/java/android/widget/Gallery.java b/core/java/android/widget/Gallery.java index d886155..cabca40 100644 --- a/core/java/android/widget/Gallery.java +++ b/core/java/android/widget/Gallery.java @@ -56,7 +56,13 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList private static final String TAG = "Gallery"; private static final boolean localLOGV = Config.LOGV; - + + /** + * Duration in milliseconds from the start of a scroll during which we're + * unsure whether the user is scrolling or flinging. + */ + private static final int SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT = 250; + /** * Horizontal spacing between items. */ @@ -104,6 +110,17 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList * Executes the delta scrolls from a fling or scroll movement. */ private FlingRunnable mFlingRunnable = new FlingRunnable(); + + /** + * Sets mSuppressSelectionChanged = false. This is used to set it to false + * in the future. It will also trigger a selection changed. + */ + private Runnable mDisableSuppressSelectionChangedRunnable = new Runnable() { + public void run() { + mSuppressSelectionChanged = false; + selectionChanged(); + } + }; /** * When fling runnable runs, it resets this to false. Any method along the @@ -142,6 +159,12 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList private boolean mReceivedInvokeKeyDown; private AdapterContextMenuInfo mContextMenuInfo; + + /** + * If true, this onScroll is the first for this user's drag (remember, a + * drag sends many onScrolls). + */ + private boolean mIsFirstScroll; public Gallery(Context context) { this(context, null); @@ -861,8 +884,13 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { if (!mShouldCallbackDuringFling) { + // We want to suppress selection changes + + // Remove any future code to set mSuppressSelectionChanged = false + removeCallbacks(mDisableSuppressSelectionChangedRunnable); + // This will get reset once we scroll into slots - mSuppressSelectionChanged = true; + if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true; } // Fling the gallery! @@ -891,13 +919,24 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList // As the user scrolls, we want to callback selection changes so related- // info on the screen is up-to-date with the gallery's selection - if (mSuppressSelectionChanged) { - mSuppressSelectionChanged = false; + if (!mShouldCallbackDuringFling) { + if (mIsFirstScroll) { + /* + * We're not notifying the client of selection changes during + * the fling, and this scroll could possibly be a fling. Don't + * do selection changes until we're sure it is not a fling. + */ + if (!mSuppressSelectionChanged) mSuppressSelectionChanged = true; + postDelayed(mDisableSuppressSelectionChangedRunnable, SCROLL_TO_FLING_UNCERTAINTY_TIMEOUT); + } + } else { + if (mSuppressSelectionChanged) mSuppressSelectionChanged = false; } // Track the motion trackMotionScroll(-1 * (int) distanceX); - + + mIsFirstScroll = false; return true; } @@ -917,6 +956,9 @@ public class Gallery extends AbsSpinner implements GestureDetector.OnGestureList mDownTouchView.setPressed(true); } + // Reset the multiple-scroll tracking state + mIsFirstScroll = true; + // Must return true to get matching events for this down event. return true; } diff --git a/core/java/android/widget/ProgressBar.java b/core/java/android/widget/ProgressBar.java index abba6d0..2e04b5d 100644 --- a/core/java/android/widget/ProgressBar.java +++ b/core/java/android/widget/ProgressBar.java @@ -29,6 +29,7 @@ import android.graphics.drawable.ClipDrawable; import android.graphics.drawable.Drawable; import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.ShapeDrawable; +import android.graphics.drawable.StateListDrawable; import android.graphics.drawable.shapes.RoundRectShape; import android.graphics.drawable.shapes.Shape; import android.util.AttributeSet; @@ -174,7 +175,7 @@ public class ProgressBar extends View { Drawable drawable = a.getDrawable(R.styleable.ProgressBar_progressDrawable); if (drawable != null) { - drawable = tileify(drawable); + drawable = tileify(drawable, false); setProgressDrawable(drawable); } @@ -217,18 +218,12 @@ public class ProgressBar extends View { a.recycle(); } - /* - * TODO: This is almost ready to be removed. This was used to support our - * old style of progress bars with the ticks. Need to check with designers - * on whether they can give us a transparent 'tick' overlay tile for our new - * gradient-based progress bars. (We still need the ticked progress bar for - * media player apps.) I'll remove this and add XML support if they want to - * do the overlay approach. If they want to just have a separate style for - * this legacy stuff, then we can keep it around. + /** + * Converts a drawable to a tiled version of itself. It will recursively + * traverse layer and state list drawables. */ - - // TODO Remove all this once ShapeDrawable + shaders are supported through XML - private Drawable tileify(Drawable drawable) { + private Drawable tileify(Drawable drawable, boolean clip) { + if (drawable instanceof LayerDrawable) { LayerDrawable background = (LayerDrawable) drawable; final int N = background.getNumberOfLayers(); @@ -236,7 +231,7 @@ public class ProgressBar extends View { for (int i = 0; i < N; i++) { int id = background.getId(i); - outDrawables[i] = createDrawableForTile(background.getDrawable(i), + outDrawables[i] = tileify(background.getDrawable(i), (id == R.id.progress || id == R.id.secondaryProgress)); } @@ -246,30 +241,36 @@ public class ProgressBar extends View { newBg.setId(i, background.getId(i)); } - drawable = newBg; - } - return drawable; - } + return newBg; + + } else if (drawable instanceof StateListDrawable) { + StateListDrawable in = (StateListDrawable) drawable; + StateListDrawable out = new StateListDrawable(); + int numStates = in.getStateCount(); + for (int i = 0; i < numStates; i++) { + out.addState(in.getStateSet(i), tileify(in.getStateDrawable(i), clip)); + } + return out; + + } else if (drawable instanceof BitmapDrawable) { + final Bitmap tileBitmap = ((BitmapDrawable) drawable).getBitmap(); + if (mSampleTile == null) { + mSampleTile = tileBitmap; + } + + final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape()); - // TODO Remove all this once ShapeDrawable + shaders are supported through XML - private Drawable createDrawableForTile(Drawable tileDrawable, boolean clip) { - if (!(tileDrawable instanceof BitmapDrawable)) return tileDrawable; + final BitmapShader bitmapShader = new BitmapShader(tileBitmap, + Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); + shapeDrawable.getPaint().setShader(bitmapShader); - final Bitmap tileBitmap = ((BitmapDrawable) tileDrawable).getBitmap(); - if (mSampleTile == null) { - mSampleTile = tileBitmap; + return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT, + ClipDrawable.HORIZONTAL) : shapeDrawable; } - final ShapeDrawable shapeDrawable = new ShapeDrawable(getDrawableShape()); - - final BitmapShader bitmapShader = new BitmapShader(tileBitmap, - Shader.TileMode.REPEAT, Shader.TileMode.CLAMP); - shapeDrawable.getPaint().setShader(bitmapShader); - - return (clip) ? new ClipDrawable(shapeDrawable, Gravity.LEFT, - ClipDrawable.HORIZONTAL) : shapeDrawable; + return drawable; } - + Shape getDrawableShape() { final float[] roundedCorners = new float[] { 5, 5, 5, 5, 5, 5, 5, 5 }; return new RoundRectShape(roundedCorners, null, null); @@ -288,7 +289,7 @@ public class ProgressBar extends View { newBg.setOneShot(background.isOneShot()); for (int i = 0; i < N; i++) { - Drawable frame = createDrawableForTile(background.getFrame(i), true); + Drawable frame = tileify(background.getFrame(i), true); frame.setLevel(10000); newBg.addFrame(frame, background.getDuration(i)); } @@ -448,29 +449,29 @@ public class ProgressBar extends View { private int mId; private int mProgress; - private boolean mFromTouch; + private boolean mFromUser; - RefreshProgressRunnable(int id, int progress, boolean fromTouch) { + RefreshProgressRunnable(int id, int progress, boolean fromUser) { mId = id; mProgress = progress; - mFromTouch = fromTouch; + mFromUser = fromUser; } public void run() { - doRefreshProgress(mId, mProgress, mFromTouch); + doRefreshProgress(mId, mProgress, mFromUser); // Put ourselves back in the cache when we are done mRefreshProgressRunnable = this; } - public void setup(int id, int progress, boolean fromTouch) { + public void setup(int id, int progress, boolean fromUser) { mId = id; mProgress = progress; - mFromTouch = fromTouch; + mFromUser = fromUser; } } - private synchronized void doRefreshProgress(int id, int progress, boolean fromTouch) { + private synchronized void doRefreshProgress(int id, int progress, boolean fromUser) { float scale = mMax > 0 ? (float) progress / (float) mMax : 0; final Drawable d = mCurrentDrawable; if (d != null) { @@ -487,16 +488,16 @@ public class ProgressBar extends View { } if (id == R.id.progress) { - onProgressRefresh(scale, fromTouch); + onProgressRefresh(scale, fromUser); } } - void onProgressRefresh(float scale, boolean fromTouch) { + void onProgressRefresh(float scale, boolean fromUser) { } - private synchronized void refreshProgress(int id, int progress, boolean fromTouch) { + private synchronized void refreshProgress(int id, int progress, boolean fromUser) { if (mUiThreadId == Thread.currentThread().getId()) { - doRefreshProgress(id, progress, fromTouch); + doRefreshProgress(id, progress, fromUser); } else { RefreshProgressRunnable r; if (mRefreshProgressRunnable != null) { @@ -504,10 +505,10 @@ public class ProgressBar extends View { r = mRefreshProgressRunnable; // Uncache it mRefreshProgressRunnable = null; - r.setup(id, progress, fromTouch); + r.setup(id, progress, fromUser); } else { // Make a new one - r = new RefreshProgressRunnable(id, progress, fromTouch); + r = new RefreshProgressRunnable(id, progress, fromUser); } post(r); } @@ -528,7 +529,7 @@ public class ProgressBar extends View { setProgress(progress, false); } - synchronized void setProgress(int progress, boolean fromTouch) { + synchronized void setProgress(int progress, boolean fromUser) { if (mIndeterminate) { return; } @@ -543,7 +544,7 @@ public class ProgressBar extends View { if (progress != mProgress) { mProgress = progress; - refreshProgress(R.id.progress, mProgress, fromTouch); + refreshProgress(R.id.progress, mProgress, fromUser); } } @@ -836,6 +837,21 @@ public class ProgressBar extends View { resolveSize(dh, heightMeasureSpec)); } + @Override + protected void drawableStateChanged() { + super.drawableStateChanged(); + + int[] state = getDrawableState(); + + if (mProgressDrawable != null && mProgressDrawable.isStateful()) { + mProgressDrawable.setState(state); + } + + if (mIndeterminateDrawable != null && mIndeterminateDrawable.isStateful()) { + mIndeterminateDrawable.setState(state); + } + } + static class SavedState extends BaseSavedState { int progress; int secondaryProgress; diff --git a/core/java/android/widget/RatingBar.java b/core/java/android/widget/RatingBar.java index 845b542..ad5ca07 100644 --- a/core/java/android/widget/RatingBar.java +++ b/core/java/android/widget/RatingBar.java @@ -26,10 +26,14 @@ import com.android.internal.R; /** * A RatingBar is an extension of SeekBar and ProgressBar that shows a rating in - * stars. The user can touch and/or drag to set the rating when using the - * default size RatingBar. The smaller RatingBar style ({@link android.R.attr#ratingBarStyleSmall}) - * and the larger indicator-only style ({@link android.R.attr#ratingBarStyleIndicator}) - * do not support user interaction and should only be used as indicators. + * stars. The user can touch/drag or use arrow keys to set the rating when using + * the default size RatingBar. The smaller RatingBar style ( + * {@link android.R.attr#ratingBarStyleSmall}) and the larger indicator-only + * style ({@link android.R.attr#ratingBarStyleIndicator}) do not support user + * interaction and should only be used as indicators. + * <p> + * When using a RatingBar that supports user interaction, placing widgets to the + * left or right of the RatingBar is discouraged. * <p> * The number of stars set (via {@link #setNumStars(int)} or in an XML layout) * will be shown when the layout width is set to wrap content (if another layout @@ -44,17 +48,18 @@ import com.android.internal.R; * @attr ref android.R.styleable#RatingBar_isIndicator */ public class RatingBar extends AbsSeekBar { - + /** - * A callback that notifies clients when the rating has been changed. This - * includes changes that were initiated by the user through a touch gesture as well - * as changes that were initiated programmatically. + * A callback that notifies clients when the rating has been changed. This + * includes changes that were initiated by the user through a touch gesture + * or arrow key/trackball as well as changes that were initiated + * programmatically. */ public interface OnRatingBarChangeListener { /** * Notification that the rating has changed. Clients can use the - * fromTouch parameter to distinguish user-initiated changes from those + * fromUser parameter to distinguish user-initiated changes from those * that occurred programmatically. This will not be called continuously * while the user is dragging, only when the user finalizes a rating by * lifting the touch. @@ -62,10 +67,10 @@ public class RatingBar extends AbsSeekBar { * @param ratingBar The RatingBar whose rating has changed. * @param rating The current rating. This will be in the range * 0..numStars. - * @param fromTouch True if the rating change was initiated by a user's - * touch gesture. + * @param fromUser True if the rating change was initiated by a user's + * touch gesture or arrow key/horizontal trackbell movement. */ - void onRatingChanged(RatingBar ratingBar, float rating, boolean fromTouch); + void onRatingChanged(RatingBar ratingBar, float rating, boolean fromUser); } @@ -138,6 +143,7 @@ public class RatingBar extends AbsSeekBar { */ public void setIsIndicator(boolean isIndicator) { mIsUserSeekable = !isIndicator; + setFocusable(!isIndicator); } /** @@ -179,7 +185,7 @@ public class RatingBar extends AbsSeekBar { * @param rating The rating to set. */ public void setRating(float rating) { - setProgress((int) (rating * getProgressPerStar())); + setProgress(Math.round(rating * getProgressPerStar())); } /** @@ -235,14 +241,14 @@ public class RatingBar extends AbsSeekBar { } @Override - void onProgressRefresh(float scale, boolean fromTouch) { - super.onProgressRefresh(scale, fromTouch); + void onProgressRefresh(float scale, boolean fromUser) { + super.onProgressRefresh(scale, fromUser); // Keep secondary progress in sync with primary updateSecondaryProgress(getProgress()); - if (!fromTouch) { - // Callback for non-touch rating changes + if (!fromUser) { + // Callback for non-user rating changes dispatchRatingChange(false); } } @@ -291,10 +297,10 @@ public class RatingBar extends AbsSeekBar { } } - void dispatchRatingChange(boolean fromTouch) { + void dispatchRatingChange(boolean fromUser) { if (mOnRatingBarChangeListener != null) { mOnRatingBarChangeListener.onRatingChanged(this, getRating(), - fromTouch); + fromUser); } } diff --git a/core/java/android/widget/ResourceCursorAdapter.java b/core/java/android/widget/ResourceCursorAdapter.java index 456d58d..9052ae3 100644 --- a/core/java/android/widget/ResourceCursorAdapter.java +++ b/core/java/android/widget/ResourceCursorAdapter.java @@ -40,7 +40,8 @@ public abstract class ResourceCursorAdapter extends CursorAdapter { * @param context The context where the ListView associated with this * SimpleListItemFactory is running * @param layout resource identifier of a layout file that defines the views - * for this list item. + * for this list item. Unless you override them later, this will + * define both the item views and the drop down views. */ public ResourceCursorAdapter(Context context, int layout, Cursor c) { super(context, c); @@ -65,6 +66,15 @@ public abstract class ResourceCursorAdapter extends CursorAdapter { } /** + * <p>Sets the layout resource of the item views.</p> + * + * @param layout the layout resources used to create item views + */ + public void setViewResource(int layout) { + mLayout = layout; + } + + /** * <p>Sets the layout resource of the drop down views.</p> * * @param dropDownLayout the layout resources used to create drop down views diff --git a/core/java/android/widget/SeekBar.java b/core/java/android/widget/SeekBar.java index e87dc2d..dfee29b 100644 --- a/core/java/android/widget/SeekBar.java +++ b/core/java/android/widget/SeekBar.java @@ -22,33 +22,35 @@ import android.util.AttributeSet; /** - * A Seekbar is an extension of ProgressBar that adds a draggable thumb. The user can touch - * the thumb and drag left or right to set the current progress level. - * + * A SeekBar is an extension of ProgressBar that adds a draggable thumb. The user can touch + * the thumb and drag left or right to set the current progress level or use the arrow keys. + * Placing focusable widgets to the left or right of a SeekBar is discouraged. + * <p> + * Clients of the SeekBar can attach a {@link SeekBar.OnSeekBarChangeListener} to * be notified of the user's actions. - * Clients of the Seekbar can attach a {@link SeekBar.OnSeekBarChangeListener} to * * @attr ref android.R.styleable#SeekBar_thumb */ public class SeekBar extends AbsSeekBar { /** - * A callback that notifies clients when the progress level has been changed. This - * includes changes that were initiated by the user through a touch gesture as well - * as changes that were initiated programmatically. + * A callback that notifies clients when the progress level has been + * changed. This includes changes that were initiated by the user through a + * touch gesture or arrow key/trackball as well as changes that were initiated + * programmatically. */ public interface OnSeekBarChangeListener { /** - * Notification that the progress level has changed. Clients can use the fromTouch parameter + * Notification that the progress level has changed. Clients can use the fromUser parameter * to distinguish user-initiated changes from those that occurred programmatically. * * @param seekBar The SeekBar whose progress has changed * @param progress The current progress level. This will be in the range 0..max where max * was set by {@link ProgressBar#setMax(int)}. (The default value for max is 100.) - * @param fromTouch True if the progress change was initiated by a user's touch gesture. + * @param fromUser True if the progress change was initiated by the user. */ - void onProgressChanged(SeekBar seekBar, int progress, boolean fromTouch); + void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser); /** * Notification that the user has started a touch gesture. Clients may want to use this @@ -80,11 +82,11 @@ public class SeekBar extends AbsSeekBar { } @Override - void onProgressRefresh(float scale, boolean fromTouch) { - super.onProgressRefresh(scale, fromTouch); + void onProgressRefresh(float scale, boolean fromUser) { + super.onProgressRefresh(scale, fromUser); if (mOnSeekBarChangeListener != null) { - mOnSeekBarChangeListener.onProgressChanged(this, getProgress(), fromTouch); + mOnSeekBarChangeListener.onProgressChanged(this, getProgress(), fromUser); } } @@ -113,4 +115,5 @@ public class SeekBar extends AbsSeekBar { mOnSeekBarChangeListener.onStopTrackingTouch(this); } } + } diff --git a/core/java/android/widget/SimpleCursorAdapter.java b/core/java/android/widget/SimpleCursorAdapter.java index 74a9964..c1595ea 100644 --- a/core/java/android/widget/SimpleCursorAdapter.java +++ b/core/java/android/widget/SimpleCursorAdapter.java @@ -74,11 +74,12 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter { * for this list item. Thelayout file should include at least * those named views defined in "to" * @param c The database cursor. Can be null if the cursor is not available yet. - * @param from A list of column names representing the data to bind to the UI + * @param from A list of column names representing the data to bind to the UI. Can be null + * if the cursor is not available yet. * @param to The views that should display column in the "from" parameter. * These should all be TextViews. The first N views in this list * are given the values of the first N columns in the from - * parameter. + * parameter. Can be null if the cursor is not available yet. */ public SimpleCursorAdapter(Context context, int layout, Cursor c, String[] from, int[] to) { super(context, layout, c); @@ -318,20 +319,24 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter { return super.convertToString(cursor); } + /** + * Create a map from an array of strings to an array of column-id integers in mCursor. + * If mCursor is null, the array will be discarded. + * + * @param from the Strings naming the columns of interest + */ private void findColumns(String[] from) { - int i; - int count = from.length; - if (mFrom == null) { - mFrom = new int[count]; - } if (mCursor != null) { + int i; + int count = from.length; + if (mFrom == null || mFrom.length != count) { + mFrom = new int[count]; + } for (i = 0; i < count; i++) { mFrom[i] = mCursor.getColumnIndexOrThrow(from[i]); } } else { - for (i = 0; i < count; i++) { - mFrom[i] = -1; - } + mFrom = null; } } @@ -341,6 +346,24 @@ public class SimpleCursorAdapter extends ResourceCursorAdapter { // rescan columns in case cursor layout is different findColumns(mOriginalFrom); } + + /** + * Change the cursor and change the column-to-view mappings at the same time. + * + * @param c The database cursor. Can be null if the cursor is not available yet. + * @param from A list of column names representing the data to bind to the UI. Can be null + * if the cursor is not available yet. + * @param to The views that should display column in the "from" parameter. + * These should all be TextViews. The first N views in this list + * are given the values of the first N columns in the from + * parameter. Can be null if the cursor is not available yet. + */ + public void changeCursorAndColumns(Cursor c, String[] from, int[] to) { + mOriginalFrom = from; + mTo = to; + super.changeCursor(c); + findColumns(mOriginalFrom); + } /** * This class can be used by external clients of SimpleCursorAdapter diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 9e5f019..73c2b3e 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -68,6 +68,7 @@ import android.text.method.TextKeyListener; import android.text.method.TransformationMethod; import android.text.style.ParagraphStyle; import android.text.style.URLSpan; +import android.text.style.UpdateAppearance; import android.text.style.UpdateLayout; import android.text.util.Linkify; import android.util.AttributeSet; @@ -3505,7 +3506,16 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener InputMethodManager imm = InputMethodManager.peekInstance(); if (highlight != null && mInputMethodState != null && imm != null) { - imm.updateSelection(this, selStart, selEnd); + if (imm.isActive(this)) { + int candStart = -1; + int candEnd = -1; + if (mText instanceof Spannable) { + Spannable sp = (Spannable)mText; + candStart = EditableInputConnection.getComposingSpanStart(sp); + candEnd = EditableInputConnection.getComposingSpanEnd(sp); + } + imm.updateSelection(this, selStart, selEnd, candStart, candEnd); + } if (imm.isWatchingCursor(this)) { final InputMethodState ims = mInputMethodState; @@ -3762,7 +3772,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return super.onKeyUp(keyCode, event); } - @Override public InputConnection createInputConnection(EditorInfo outAttrs) { + @Override public InputConnection onCreateInputConnection(EditorInfo outAttrs) { if (mInputType != EditorInfo.TYPE_NULL) { if (mInputMethodState == null) { mInputMethodState = new InputMethodState(); @@ -5137,7 +5147,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - if (what instanceof UpdateLayout || + if (what instanceof UpdateAppearance || what instanceof ParagraphStyle) { invalidate(); mHighlightPathBogus = true; @@ -5167,6 +5177,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener public void afterTextChanged(Editable buffer) { TextView.this.sendAfterTextChanged(buffer); + + if (MetaKeyKeyListener.getMetaState(buffer, + MetaKeyKeyListener.META_SELECTING) != 0) { + MetaKeyKeyListener.stopSelecting(TextView.this, buffer); + } + TextView.this.reportExtractedText(); } @@ -5550,6 +5566,15 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return false; } + private boolean canSelectText() { + if (mText instanceof Spannable && mText.length() != 0 && + mMovement != null && mMovement.canSelectArbitrarily()) { + return true; + } + + return false; + } + private boolean canCut() { if (mTransformation instanceof PasswordTransformationMethod) { return false; @@ -5623,6 +5648,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener boolean selection = getSelectionStart() != getSelectionEnd(); + if (canSelectText()) { + if (MetaKeyKeyListener.getMetaState(mText, MetaKeyKeyListener.META_SELECTING) != 0) { + menu.add(0, ID_STOP_SELECTING_TEXT, 0, + com.android.internal.R.string.stopSelectingText). + setOnMenuItemClickListener(handler); + added = true; + } else { + menu.add(0, ID_SELECT_TEXT, 0, + com.android.internal.R.string.selectText). + setOnMenuItemClickListener(handler); + added = true; + } + } + if (canCut()) { int name; if (selection) { @@ -5688,6 +5727,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private static final int ID_SELECT_ALL = com.android.internal.R.id.selectAll; + private static final int ID_SELECT_TEXT = com.android.internal.R.id.selectText; + private static final int ID_STOP_SELECTING_TEXT = com.android.internal.R.id.stopSelectingText; private static final int ID_CUT = com.android.internal.R.id.cut; private static final int ID_COPY = com.android.internal.R.id.copy; private static final int ID_PASTE = com.android.internal.R.id.paste; @@ -5728,7 +5769,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mText.length()); return true; + case ID_SELECT_TEXT: + MetaKeyKeyListener.startSelecting(this, (Spannable) mText); + return true; + + case ID_STOP_SELECTING_TEXT: + MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); + Selection.setSelection((Spannable) mText, getSelectionEnd()); + return true; + case ID_CUT: + MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); + if (min == max) { min = 0; max = mText.length(); @@ -5739,6 +5791,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return true; case ID_COPY: + MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); + if (min == max) { min = 0; max = mText.length(); @@ -5748,6 +5802,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return true; case ID_PASTE: + MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); + CharSequence paste = clip.getText(); if (paste != null) { @@ -5758,6 +5814,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return true; case ID_COPY_URL: + MetaKeyKeyListener.stopSelecting(this, (Spannable) mText); + URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class); if (urls.length == 1) { diff --git a/core/java/com/android/internal/app/RingtonePickerActivity.java b/core/java/com/android/internal/app/RingtonePickerActivity.java index 9c83aa3..1f8e6f0 100644 --- a/core/java/com/android/internal/app/RingtonePickerActivity.java +++ b/core/java/com/android/internal/app/RingtonePickerActivity.java @@ -169,6 +169,14 @@ public final class RingtonePickerActivity extends AlertActivity implements public void onPrepareListView(ListView listView) { + if (mHasDefaultItem) { + mDefaultRingtonePos = addDefaultRingtoneItem(listView); + + if (RingtoneManager.isDefault(mExistingUri)) { + mClickedPos = mDefaultRingtonePos; + } + } + if (mHasSilentItem) { mSilentPos = addSilentItem(listView); @@ -178,14 +186,6 @@ public final class RingtonePickerActivity extends AlertActivity implements } } - if (mHasDefaultItem) { - mDefaultRingtonePos = addDefaultRingtoneItem(listView); - - if (RingtoneManager.isDefault(mExistingUri)) { - mClickedPos = mDefaultRingtonePos; - } - } - if (mClickedPos == -1) { mClickedPos = getListPosition(mRingtoneManager.getRingtonePosition(mExistingUri)); } diff --git a/core/java/com/android/internal/os/HandlerCaller.java b/core/java/com/android/internal/os/HandlerCaller.java index fd8fd5a..e88a36f 100644 --- a/core/java/com/android/internal/os/HandlerCaller.java +++ b/core/java/com/android/internal/os/HandlerCaller.java @@ -26,6 +26,8 @@ public class HandlerCaller { public int argi2; public int argi3; public int argi4; + public int argi5; + public int argi6; } static final int ARGS_POOL_MAX_SIZE = 10; @@ -153,6 +155,18 @@ public class HandlerCaller { return mH.obtainMessage(what, 0, 0, args); } + public Message obtainMessageIIIIII(int what, int arg1, int arg2, + int arg3, int arg4, int arg5, int arg6) { + SomeArgs args = obtainArgs(); + args.argi1 = arg1; + args.argi2 = arg2; + args.argi3 = arg3; + args.argi4 = arg4; + args.argi5 = arg5; + args.argi6 = arg6; + return mH.obtainMessage(what, 0, 0, args); + } + public Message obtainMessageIIIIO(int what, int arg1, int arg2, int arg3, int arg4, Object arg5) { SomeArgs args = obtainArgs(); diff --git a/core/java/com/android/internal/view/IInputConnectionWrapper.java b/core/java/com/android/internal/view/IInputConnectionWrapper.java index c5966ee..d604259 100644 --- a/core/java/com/android/internal/view/IInputConnectionWrapper.java +++ b/core/java/com/android/internal/view/IInputConnectionWrapper.java @@ -23,6 +23,7 @@ public class IInputConnectionWrapper extends IInputContext.Stub { private static final int DO_COMMIT_TEXT = 50; private static final int DO_COMMIT_COMPLETION = 55; private static final int DO_SET_COMPOSING_TEXT = 60; + private static final int DO_FINISH_COMPOSING_TEXT = 65; private static final int DO_SEND_KEY_EVENT = 70; private static final int DO_DELETE_SURROUNDING_TEXT = 80; private static final int DO_HIDE_STATUS_ICON = 100; @@ -89,6 +90,10 @@ public class IInputConnectionWrapper extends IInputContext.Stub { dispatchMessage(obtainMessageIO(DO_SET_COMPOSING_TEXT, newCursorPosition, text)); } + public void finishComposingText() { + dispatchMessage(obtainMessage(DO_FINISH_COMPOSING_TEXT)); + } + public void sendKeyEvent(KeyEvent event) { dispatchMessage(obtainMessageO(DO_SEND_KEY_EVENT, event)); } @@ -181,6 +186,10 @@ public class IInputConnectionWrapper extends IInputContext.Stub { mInputConnection.setComposingText((CharSequence)msg.obj, msg.arg1); return; } + case DO_FINISH_COMPOSING_TEXT: { + mInputConnection.finishComposingText(); + return; + } case DO_SEND_KEY_EVENT: { mInputConnection.sendKeyEvent((KeyEvent)msg.obj); return; diff --git a/core/java/com/android/internal/view/IInputContext.aidl b/core/java/com/android/internal/view/IInputContext.aidl index 7ea65a0..55a9784 100644 --- a/core/java/com/android/internal/view/IInputContext.aidl +++ b/core/java/com/android/internal/view/IInputContext.aidl @@ -42,6 +42,8 @@ import com.android.internal.view.IInputContextCallback; void setComposingText(CharSequence text, int newCursorPosition); + void finishComposingText(); + void commitText(CharSequence text, int newCursorPosition); void commitCompletion(in CompletionInfo completion); diff --git a/core/java/com/android/internal/view/IInputMethodManager.aidl b/core/java/com/android/internal/view/IInputMethodManager.aidl index b4cfe26..1c9e797 100644 --- a/core/java/com/android/internal/view/IInputMethodManager.aidl +++ b/core/java/com/android/internal/view/IInputMethodManager.aidl @@ -47,5 +47,7 @@ interface IInputMethodManager { void setInputMethod(in IBinder token, String id); void hideMySoftInput(in IBinder token); void updateStatusIcon(int iconId, String iconPackage); + + boolean setInputMethodEnabled(String id, boolean enabled); } diff --git a/core/java/com/android/internal/view/IInputMethodSession.aidl b/core/java/com/android/internal/view/IInputMethodSession.aidl index 4f28593..8a44976 100644 --- a/core/java/com/android/internal/view/IInputMethodSession.aidl +++ b/core/java/com/android/internal/view/IInputMethodSession.aidl @@ -34,7 +34,8 @@ oneway interface IInputMethodSession { void updateExtractedText(int token, in ExtractedText text); void updateSelection(int oldSelStart, int oldSelEnd, - int newSelStart, int newSelEnd); + int newSelStart, int newSelEnd, + int candidatesStart, int candidatesEnd); void updateCursor(in Rect newCursor); diff --git a/core/java/com/android/internal/view/InputConnectionWrapper.java b/core/java/com/android/internal/view/InputConnectionWrapper.java index 5bfcfe9..1cfaf17 100644 --- a/core/java/com/android/internal/view/InputConnectionWrapper.java +++ b/core/java/com/android/internal/view/InputConnectionWrapper.java @@ -250,6 +250,15 @@ public class InputConnectionWrapper implements InputConnection { } } + public boolean finishComposingText() { + try { + mIInputContext.finishComposingText(); + return true; + } catch (RemoteException e) { + return false; + } + } + public boolean sendKeyEvent(KeyEvent event) { try { mIInputContext.sendKeyEvent(event); diff --git a/core/java/com/android/internal/view/menu/MenuItemImpl.java b/core/java/com/android/internal/view/menu/MenuItemImpl.java index 43dba6f..1543b62 100644 --- a/core/java/com/android/internal/view/menu/MenuItemImpl.java +++ b/core/java/com/android/internal/view/menu/MenuItemImpl.java @@ -547,19 +547,7 @@ public final class MenuItemImpl implements MenuItem { boolean setVisibleInt(boolean shown) { final int oldFlags = mFlags; mFlags = (mFlags & ~HIDDEN) | (shown ? 0 : HIDDEN); - if (oldFlags != mFlags) { - final int visibility = (shown) ? View.VISIBLE : View.GONE; - - for (int i = MenuBuilder.NUM_TYPES - 1; i >= 0; i--) { - if (hasItemView(i)) { - ((View) mItemViews[i].get()).setVisibility(visibility); - } - } - - return true; - } - - return false; + return oldFlags != mFlags; } public MenuItem setVisible(boolean shown) { diff --git a/core/java/com/android/internal/widget/EditableInputConnection.java b/core/java/com/android/internal/widget/EditableInputConnection.java index efe15f3..263220b 100644 --- a/core/java/com/android/internal/widget/EditableInputConnection.java +++ b/core/java/com/android/internal/widget/EditableInputConnection.java @@ -44,22 +44,74 @@ public class EditableInputConnection extends BaseInputConnection { public static final Object COMPOSING = new ComposingText(); private final TextView mTextView; - private final Handler mUiHandler; private Object[] mDefaultComposingSpans; public EditableInputConnection(TextView textview) { super(textview); mTextView = textview; - mUiHandler = textview.getHandler(); } + public static final void removeComposingSpans(Spannable text) { + text.removeSpan(COMPOSING); + Object[] sps = text.getSpans(0, text.length(), Object.class); + if (sps != null) { + for (int i=sps.length-1; i>=0; i--) { + Object o = sps[i]; + if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) { + text.removeSpan(o); + } + } + } + } + + public static void setComposingSpans(Spannable text) { + final Object[] sps = text.getSpans(0, text.length(), Object.class); + if (sps != null) { + for (int i=sps.length-1; i>=0; i--) { + final Object o = sps[i]; + if (o == COMPOSING) { + text.removeSpan(o); + continue; + } + final int fl = text.getSpanFlags(o); + if ((fl&(Spanned.SPAN_COMPOSING|Spanned.SPAN_POINT_MARK_MASK)) + != (Spanned.SPAN_COMPOSING|Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) { + text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o), + (fl&Spanned.SPAN_POINT_MARK_MASK) + | Spanned.SPAN_COMPOSING + | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + } + + text.setSpan(COMPOSING, 0, text.length(), + Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); + } + + public static int getComposingSpanStart(Spannable text) { + return text.getSpanStart(COMPOSING); + } + + public static int getComposingSpanEnd(Spannable text) { + return text.getSpanEnd(COMPOSING); + } + public boolean setComposingText(CharSequence text, int newCursorPosition) { if (DEBUG) Log.v(TAG, "setComposingText " + text); replaceText(text, newCursorPosition, true); return true; } + public boolean finishComposingText() { + if (DEBUG) Log.v(TAG, "finishComposingText"); + final Editable content = getEditable(); + if (content != null) { + removeComposingSpans(content); + } + return true; + } + public boolean commitText(CharSequence text, int newCursorPosition) { if (DEBUG) Log.v(TAG, "commitText " + text); replaceText(text, newCursorPosition, false); @@ -212,43 +264,6 @@ public class EditableInputConnection extends BaseInputConnection { return null; } - public static void setComposingSpans(Spannable text) { - final Object[] sps = text.getSpans(0, text.length(), Object.class); - if (sps != null) { - for (int i=sps.length-1; i>=0; i--) { - final Object o = sps[i]; - if (o == COMPOSING) { - text.removeSpan(o); - continue; - } - final int fl = text.getSpanFlags(o); - if ((fl&(Spanned.SPAN_COMPOSING|Spanned.SPAN_POINT_MARK_MASK)) - != (Spanned.SPAN_COMPOSING|Spanned.SPAN_EXCLUSIVE_EXCLUSIVE)) { - text.setSpan(o, text.getSpanStart(o), text.getSpanEnd(o), - (fl&Spanned.SPAN_POINT_MARK_MASK) - | Spanned.SPAN_COMPOSING - | Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); - } - } - } - - text.setSpan(COMPOSING, 0, text.length(), - Spanned.SPAN_EXCLUSIVE_EXCLUSIVE | Spanned.SPAN_COMPOSING); - } - - public static final void removeComposingSpans(Spannable text) { - text.removeSpan(COMPOSING); - Object[] sps = text.getSpans(0, text.length(), Object.class); - if (sps != null) { - for (int i=sps.length-1; i>=0; i--) { - Object o = sps[i]; - if ((text.getSpanFlags(o)&Spanned.SPAN_COMPOSING) != 0) { - text.removeSpan(o); - } - } - } - } - private void replaceText(CharSequence text, int newCursorPosition, boolean composing) { final Editable content = getEditable(); diff --git a/core/java/com/google/android/gdata/client/AndroidGDataClient.java b/core/java/com/google/android/gdata/client/AndroidGDataClient.java index 48d0233..17f86d6 100644 --- a/core/java/com/google/android/gdata/client/AndroidGDataClient.java +++ b/core/java/com/google/android/gdata/client/AndroidGDataClient.java @@ -49,8 +49,7 @@ public class AndroidGDataClient implements GDataClient { private static final String X_HTTP_METHOD_OVERRIDE = "X-HTTP-Method-Override"; - private static final String USER_AGENT_GZIP = - GoogleHttpClient.getGzipCapableUserAgent("Android-GData/1.0"); + private static final String USER_AGENT_APP_VERSION = "Android-GData/1.0"; private static final int MAX_REDIRECTS = 10; @@ -123,7 +122,8 @@ public class AndroidGDataClient implements GDataClient { * through the Android proxy server, using null to indicate not using proxy. */ public AndroidGDataClient(ContentResolver resolver) { - mHttpClient = new GoogleHttpClient(resolver, USER_AGENT_GZIP); + mHttpClient = new GoogleHttpClient(resolver, USER_AGENT_APP_VERSION, + true /* gzip capable */); mResolver = resolver; } diff --git a/core/java/com/google/android/net/GoogleHttpClient.java b/core/java/com/google/android/net/GoogleHttpClient.java index bc6eaee..4656aff 100644 --- a/core/java/com/google/android/net/GoogleHttpClient.java +++ b/core/java/com/google/android/net/GoogleHttpClient.java @@ -38,13 +38,12 @@ import java.net.URISyntaxException; import android.content.ContentResolver; import android.content.ContentValues; import android.os.SystemClock; +import android.os.Build; import android.net.http.AndroidHttpClient; import android.provider.Checkin; import android.util.Config; import android.util.Log; -import com.android.internal.os.RuntimeInit; - /** * {@link AndroidHttpClient} wrapper that uses {@link UrlRules} to rewrite URLs * and otherwise tweak HTTP requests. @@ -69,6 +68,7 @@ public class GoogleHttpClient implements HttpClient { * Create an HTTP client. Normally one client is shared throughout an app. * @param resolver to use for accessing URL rewriting rules. * @param userAgent to report in your HTTP requests. + * @deprecated Use {@link #GoogleHttpClient(android.content.ContentResolver, String, boolean)} */ public GoogleHttpClient(ContentResolver resolver, String userAgent) { mClient = AndroidHttpClient.newInstance(userAgent); @@ -77,6 +77,35 @@ public class GoogleHttpClient implements HttpClient { } /** + * Create an HTTP client. Normaly this client is shared throughout an app. + * The HTTP client will construct its User-Agent as follows: + * + * <appAndVersion> (<build device> <build id>) + * or + * <appAndVersion> (<build device> <build id>); gzip + * (if gzip capable) + * + * @param resolver to use for acccessing URL rewriting rules. + * @param appAndVersion Base app and version to use in the User-Agent. + * e.g., "MyApp/1.0" + * @param gzipCapable Whether or not this client is able to consume gzip'd + * responses. Only used to modify the User-Agent, not other request + * headers. Needed because Google servers require gzip in the User-Agent + * in order to return gzip'd content. + */ + public GoogleHttpClient(ContentResolver resolver, String appAndVersion, + boolean gzipCapable) { + String userAgent = appAndVersion + + " (" + Build.DEVICE + " " + Build.ID + ")"; + if (gzipCapable) { + userAgent = userAgent + "; gzip"; + } + mClient = AndroidHttpClient.newInstance(userAgent); + mResolver = resolver; + mUserAgent = userAgent; + } + + /** * Release resources associated with this client. You must call this, * or significant resources (sockets and memory) may be leaked. */ @@ -181,6 +210,7 @@ public class GoogleHttpClient implements HttpClient { * * @param originalUserAgent to modify (however you identify yourself) * @return user agent with a "yes, I really can handle gzip" token added. + * @deprecated Use {@link #GoogleHttpClient(android.content.ContentResolver, String, boolean)} */ public static String getGzipCapableUserAgent(String originalUserAgent) { return originalUserAgent + "; gzip"; |