summaryrefslogtreecommitdiffstats
path: root/core/java
diff options
context:
space:
mode:
Diffstat (limited to 'core/java')
-rw-r--r--core/java/android/app/Activity.java2
-rw-r--r--core/java/android/app/SearchDialog.java960
-rw-r--r--core/java/android/app/SearchManager.java8
-rw-r--r--core/java/android/bluetooth/BluetoothA2dp.java4
-rw-r--r--core/java/android/content/AbstractTableMerger.java12
-rw-r--r--core/java/android/hardware/Camera.java106
-rw-r--r--core/java/android/inputmethodservice/IInputMethodSessionWrapper.java9
-rw-r--r--core/java/android/inputmethodservice/InputMethodService.java177
-rwxr-xr-xcore/java/android/inputmethodservice/Keyboard.java10
-rwxr-xr-xcore/java/android/inputmethodservice/KeyboardView.java50
-rw-r--r--core/java/android/os/HandlerState.java33
-rw-r--r--core/java/android/os/HandlerStateMachine.java290
-rw-r--r--core/java/android/os/IPowerManager.aidl1
-rw-r--r--core/java/android/os/Power.java18
-rw-r--r--core/java/android/preference/PreferenceGroupAdapter.java5
-rw-r--r--core/java/android/provider/Browser.java10
-rw-r--r--core/java/android/provider/Contacts.java30
-rw-r--r--core/java/android/provider/MediaStore.java46
-rw-r--r--core/java/android/provider/Settings.java12
-rw-r--r--core/java/android/provider/Sync.java30
-rw-r--r--core/java/android/server/BluetoothA2dpService.java31
-rw-r--r--core/java/android/server/search/SearchableInfo.java18
-rw-r--r--core/java/android/speech/RecognizerIntent.java106
-rw-r--r--core/java/android/text/InputType.java32
-rw-r--r--core/java/android/text/Layout.java4
-rw-r--r--core/java/android/text/format/DateUtils.java201
-rw-r--r--core/java/android/text/method/ArrowKeyMovementMethod.java36
-rw-r--r--core/java/android/text/method/MetaKeyKeyListener.java239
-rw-r--r--core/java/android/text/style/BackgroundColorSpan.java2
-rw-r--r--core/java/android/text/style/CharacterStyle.java4
-rw-r--r--core/java/android/text/style/ClickableSpan.java2
-rw-r--r--core/java/android/text/style/DynamicDrawableSpan.java50
-rw-r--r--core/java/android/text/style/ForegroundColorSpan.java3
-rw-r--r--core/java/android/text/style/ImageSpan.java46
-rw-r--r--core/java/android/text/style/MaskFilterSpan.java3
-rw-r--r--core/java/android/text/style/RasterizerSpan.java3
-rw-r--r--core/java/android/text/style/StrikethroughSpan.java2
-rw-r--r--core/java/android/text/style/UnderlineSpan.java2
-rw-r--r--core/java/android/text/style/UpdateAppearance.java10
-rw-r--r--core/java/android/text/style/UpdateLayout.java7
-rw-r--r--core/java/android/util/PrintStreamPrinter.java40
-rw-r--r--core/java/android/view/Menu.java3
-rw-r--r--core/java/android/view/SurfaceView.java62
-rw-r--r--core/java/android/view/View.java29
-rw-r--r--core/java/android/view/ViewRoot.java66
-rw-r--r--core/java/android/view/animation/Animation.java37
-rwxr-xr-xcore/java/android/view/animation/package.html236
-rw-r--r--core/java/android/view/inputmethod/BaseInputConnection.java21
-rw-r--r--core/java/android/view/inputmethod/DefaultInputMethod.java6
-rw-r--r--core/java/android/view/inputmethod/InputConnection.java11
-rw-r--r--core/java/android/view/inputmethod/InputConnectionWrapper.java4
-rw-r--r--core/java/android/view/inputmethod/InputMethod.java18
-rw-r--r--core/java/android/view/inputmethod/InputMethodManager.java232
-rw-r--r--core/java/android/view/inputmethod/InputMethodSession.java7
-rw-r--r--core/java/android/view/inputmethod/package.html3
-rw-r--r--core/java/android/webkit/CookieManager.java85
-rw-r--r--core/java/android/webkit/TextDialog.java59
-rw-r--r--core/java/android/webkit/WebView.java81
-rw-r--r--core/java/android/webkit/WebViewCore.java79
-rw-r--r--core/java/android/widget/AbsListView.java23
-rw-r--r--core/java/android/widget/AbsSeekBar.java31
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java22
-rw-r--r--core/java/android/widget/Gallery.java52
-rw-r--r--core/java/android/widget/ProgressBar.java112
-rw-r--r--core/java/android/widget/RatingBar.java44
-rw-r--r--core/java/android/widget/ResourceCursorAdapter.java12
-rw-r--r--core/java/android/widget/SeekBar.java29
-rw-r--r--core/java/android/widget/SimpleCursorAdapter.java43
-rw-r--r--core/java/android/widget/TextView.java64
-rw-r--r--core/java/com/android/internal/app/RingtonePickerActivity.java16
-rw-r--r--core/java/com/android/internal/os/HandlerCaller.java14
-rw-r--r--core/java/com/android/internal/view/IInputConnectionWrapper.java9
-rw-r--r--core/java/com/android/internal/view/IInputContext.aidl2
-rw-r--r--core/java/com/android/internal/view/IInputMethodManager.aidl2
-rw-r--r--core/java/com/android/internal/view/IInputMethodSession.aidl3
-rw-r--r--core/java/com/android/internal/view/InputConnectionWrapper.java9
-rw-r--r--core/java/com/android/internal/view/menu/MenuItemImpl.java14
-rw-r--r--core/java/com/android/internal/widget/EditableInputConnection.java93
-rw-r--r--core/java/com/google/android/gdata/client/AndroidGDataClient.java6
-rw-r--r--core/java/com/google/android/net/GoogleHttpClient.java34
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&mdash;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 (&quot;50&quot;
- for 50% relative to the parent, &quot;50%&quot; 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>&lt;set android:shareInterpolator=&quot;true&quot;
- android:interpolator=&quot;@android:anim/accelerate_interpolator&quot;&gt;
+ </p>
- &lt;translate android:fromXDelta=&quot;0&quot;
- android:toXDelta=&quot;30&quot;
- android:duration=&quot;800&quot;
- android:fillAfter=&quot;true&quot;/&gt;
-
- &lt;set android:duration="800"
- android:pivotX=&quot;50%&quot;
- android:pivotY=&quot;50%&quot; &gt;
+<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>
- &lt;rotate android:fromDegrees=&quot;0&quot;
- android:toDegrees=&quot;-90&quot;
- android:fillAfter=&quot;true&quot;
- android:startOffset=&quot;800&quot;/&gt;
-
- &lt;scale android:fromXScale=&quot;1.0&quot;
- android:toXScale=&quot;2.0&quot;
- android:fromYScale=&quot;1.0&quot;
- android:toYScale=&quot;2.0&quot;
- android:startOffset=&quot;800&quot; /&gt;
- &lt;/set&gt;
-
- &lt;translate android:toYDelta=&quot;-100&quot;
- android:fillAfter=&quot;true&quot;
- android:duration=&quot;800&quot;
- android:startOffset=&quot;1600&quot;/&gt;
-&lt;/set&gt;</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>&nbsp;</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 &quot;L&quot; 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>&nbsp; </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";