diff options
Diffstat (limited to 'core/java')
98 files changed, 2614 insertions, 1133 deletions
diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index b4471f0..7994d7c 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -1092,6 +1092,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeNoException(); return true; } + + case KILL_ALL_BACKGROUND_PROCESSES_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + killAllBackgroundProcesses(); + reply.writeNoException(); + return true; + } case FORCE_STOP_PACKAGE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); @@ -2906,7 +2913,7 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } - + public void killBackgroundProcesses(String packageName) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); @@ -2917,7 +2924,17 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } - + + public void killAllBackgroundProcesses() throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + mRemote.transact(KILL_ALL_BACKGROUND_PROCESSES_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void forceStopPackage(String packageName) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 8afe9bf..a4714ca 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -2769,7 +2769,9 @@ public final class ActivityThread { if (info != null) { try { // First create a thumbnail for the activity... - info.thumbnail = createThumbnailBitmap(r); + // For now, don't create the thumbnail here; we are + // doing that by doing a screen snapshot. + info.thumbnail = null; //createThumbnailBitmap(r); info.description = r.activity.onCreateDescription(); } catch (Exception e) { if (!mInstrumentation.onException(r.activity, e)) { @@ -3830,11 +3832,16 @@ public final class ActivityThread { * Initialize the default http proxy in this process for the reasons we set the time zone. */ IBinder b = ServiceManager.getService(Context.CONNECTIVITY_SERVICE); - IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); - try { - ProxyProperties proxyProperties = service.getProxy(); - Proxy.setHttpProxySystemProperty(proxyProperties); - } catch (RemoteException e) {} + if (b != null) { + // In pre-boot mode (doing initial launch to collect password), not + // all system is up. This includes the connectivity service, so don't + // crash if we can't get it. + IConnectivityManager service = IConnectivityManager.Stub.asInterface(b); + try { + ProxyProperties proxyProperties = service.getProxy(); + Proxy.setHttpProxySystemProperty(proxyProperties); + } catch (RemoteException e) {} + } if (data.instrumentationName != null) { ContextImpl appContext = new ContextImpl(); diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 7a69419..f1ce2bb 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -28,6 +28,7 @@ import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.util.Log; import android.util.TypedValue; import android.view.ActionMode; import android.view.ContextMenu; @@ -77,6 +78,7 @@ import java.lang.ref.WeakReference; */ public class Dialog implements DialogInterface, Window.Callback, KeyEvent.Callback, OnCreateContextMenuListener { + private static final String TAG = "Dialog"; private Activity mOwnerActivity; final Context mContext; @@ -110,6 +112,8 @@ public class Dialog implements DialogInterface, Window.Callback, private Handler mListenersHandler; + private ActionMode mActionMode; + private final Runnable mDismissAction = new Runnable() { public void run() { dismissDialog(); @@ -298,18 +302,27 @@ public class Dialog implements DialogInterface, Window.Callback, if (Thread.currentThread() != mUiThread) { mHandler.post(mDismissAction); } else { + mHandler.removeCallbacks(mDismissAction); mDismissAction.run(); } } - private void dismissDialog() { + void dismissDialog() { if (mDecor == null || !mShowing) { return; } + if (mWindow.isDestroyed()) { + Log.e(TAG, "Tried to dismissDialog() but the Dialog's window was already destroyed!"); + return; + } + try { mWindowManager.removeView(mDecor); } finally { + if (mActionMode != null) { + mActionMode.finish(); + } mDecor = null; mWindow.closeAllPanels(); onStop(); @@ -952,10 +965,26 @@ public class Dialog implements DialogInterface, Window.Callback, return null; } + /** + * {@inheritDoc} + * + * Note that if you override this method you should always call through + * to the superclass implementation by calling super.onActionModeStarted(mode). + */ public void onActionModeStarted(ActionMode mode) { + mActionMode = mode; } + /** + * {@inheritDoc} + * + * Note that if you override this method you should always call through + * to the superclass implementation by calling super.onActionModeFinished(mode). + */ public void onActionModeFinished(ActionMode mode) { + if (mode == mActionMode) { + mActionMode = null; + } } /** diff --git a/core/java/android/app/Fragment.java b/core/java/android/app/Fragment.java index d423d98..473a2d1 100644 --- a/core/java/android/app/Fragment.java +++ b/core/java/android/app/Fragment.java @@ -339,6 +339,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene private static final HashMap<String, Class<?>> sClassMap = new HashMap<String, Class<?>>(); + static final int INVALID_STATE = -1; // Invalid state used as a null value. static final int INITIALIZING = 0; // Not yet created. static final int CREATED = 1; // Created. static final int ACTIVITY_CREATED = 2; // The activity has finished its creation. @@ -403,7 +404,7 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene // The fragment manager we are associated with. Set as soon as the // fragment is used in a transaction; cleared after it has been removed // from all transactions. - FragmentManager mFragmentManager; + FragmentManagerImpl mFragmentManager; // Activity this fragment is attached to. Activity mActivity; @@ -453,6 +454,13 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene // The View generated for this fragment. View mView; + // Whether this fragment should defer starting until after other fragments + // have been started and their loaders are finished. + boolean mDeferStart; + + // Hint provided by the app that this fragment is currently visible to the user. + boolean mUserVisibleHint = true; + LoaderManagerImpl mLoaderManager; boolean mLoadersStarted; boolean mCheckedForLoaderManager; @@ -910,6 +918,35 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene } /** + * Set a hint to the system about whether this fragment's UI is currently visible + * to the user. This hint defaults to true and is persistent across fragment instance + * state save and restore. + * + * <p>An app may set this to false to indicate that the fragment's UI is + * scrolled out of visibility or is otherwise not directly visible to the user. + * This may be used by the system to prioritize operations such as fragment lifecycle updates + * or loader ordering behavior.</p> + * + * @param isVisibleToUser true if this fragment's UI is currently visible to the user (default), + * false if it is not. + */ + public void setUserVisibleHint(boolean isVisibleToUser) { + if (!mUserVisibleHint && isVisibleToUser && mState < STARTED) { + mFragmentManager.performPendingDeferredStart(this); + } + mUserVisibleHint = isVisibleToUser; + mDeferStart = !isVisibleToUser; + } + + /** + * @return The current value of the user-visible hint on this fragment. + * @see #setUserVisibleHint(boolean) + */ + public boolean getUserVisibleHint() { + return mUserVisibleHint; + } + + /** * Return the LoaderManager for this fragment, creating it if needed. */ public LoaderManager getLoaderManager() { @@ -1444,7 +1481,8 @@ public class Fragment implements ComponentCallbacks2, OnCreateContextMenuListene writer.print(" mMenuVisible="); writer.print(mMenuVisible); writer.print(" mHasMenu="); writer.println(mHasMenu); writer.print(prefix); writer.print("mRetainInstance="); writer.print(mRetainInstance); - writer.print(" mRetaining="); writer.println(mRetaining); + writer.print(" mRetaining="); writer.print(mRetaining); + writer.print(" mUserVisibleHint="); writer.println(mUserVisibleHint); if (mFragmentManager != null) { writer.print(prefix); writer.print("mFragmentManager="); writer.println(mFragmentManager); diff --git a/core/java/android/app/FragmentManager.java b/core/java/android/app/FragmentManager.java index 3da4f29..a8c9cba 100644 --- a/core/java/android/app/FragmentManager.java +++ b/core/java/android/app/FragmentManager.java @@ -382,6 +382,7 @@ final class FragmentManagerImpl extends FragmentManager { static final String TARGET_REQUEST_CODE_STATE_TAG = "android:target_req_state"; static final String TARGET_STATE_TAG = "android:target_state"; static final String VIEW_STATE_TAG = "android:view_state"; + static final String USER_VISIBLE_HINT_TAG = "android:user_visible_hint"; ArrayList<Runnable> mPendingActions; Runnable[] mTmpActions; @@ -406,6 +407,7 @@ final class FragmentManagerImpl extends FragmentManager { boolean mStateSaved; boolean mDestroyed; String mNoTransactionsBecause; + boolean mHavePendingDeferredStart; // Temporary vars for state save and restore. Bundle mStateBundle = null; @@ -709,6 +711,18 @@ final class FragmentManagerImpl extends FragmentManager { return AnimatorInflater.loadAnimator(mActivity, anim); } + public void performPendingDeferredStart(Fragment f) { + if (f.mDeferStart) { + if (mExecutingActions) { + // Wait until we're done executing our pending transactions + mHavePendingDeferredStart = true; + return; + } + f.mDeferStart = false; + moveToState(f, mCurState, 0, 0); + } + } + void moveToState(Fragment f, int newState, int transit, int transitionStyle) { // Fragments that are not currently added will sit in the onCreate() state. if (!f.mAdded && newState > Fragment.CREATED) { @@ -718,7 +732,11 @@ final class FragmentManagerImpl extends FragmentManager { // While removing a fragment, we can't change it to a higher state. newState = f.mState; } - + // Defer start if requested; don't allow it to move to STARTED or higher + // if it's not already started. + if (f.mDeferStart && f.mState < Fragment.STARTED && newState > Fragment.STOPPED) { + newState = Fragment.STOPPED; + } if (f.mState < newState) { // For fragments that are created from a layout, when restoring from // state we don't want to allow them to be created until they are @@ -746,6 +764,14 @@ final class FragmentManagerImpl extends FragmentManager { f.mTargetRequestCode = f.mSavedFragmentState.getInt( FragmentManagerImpl.TARGET_REQUEST_CODE_STATE_TAG, 0); } + f.mUserVisibleHint = f.mSavedFragmentState.getBoolean( + FragmentManagerImpl.USER_VISIBLE_HINT_TAG, true); + if (!f.mUserVisibleHint) { + f.mDeferStart = true; + if (newState > Fragment.STOPPED) { + newState = Fragment.STOPPED; + } + } } f.mActivity = mActivity; f.mFragmentManager = mActivity.mFragments; @@ -992,13 +1018,21 @@ final class FragmentManagerImpl extends FragmentManager { mCurState = newState; if (mActive != null) { + boolean loadersRunning = false; for (int i=0; i<mActive.size(); i++) { Fragment f = mActive.get(i); if (f != null) { moveToState(f, newState, transit, transitStyle); + if (f.mLoaderManager != null) { + loadersRunning |= f.mLoaderManager.hasRunningLoaders(); + } } } + if (!loadersRunning) { + startPendingDeferredFragments(); + } + if (mNeedMenuInvalidate && mActivity != null && mCurState == Fragment.RESUMED) { mActivity.invalidateOptionsMenu(); mNeedMenuInvalidate = false; @@ -1006,6 +1040,17 @@ final class FragmentManagerImpl extends FragmentManager { } } + void startPendingDeferredFragments() { + if (mActive == null) return; + + for (int i=0; i<mActive.size(); i++) { + Fragment f = mActive.get(i); + if (f != null) { + performPendingDeferredStart(f); + } + } + } + void makeActive(Fragment f) { if (f.mIndex >= 0) { return; @@ -1313,7 +1358,7 @@ final class FragmentManagerImpl extends FragmentManager { synchronized (this) { if (mPendingActions == null || mPendingActions.size() == 0) { - return didSomething; + break; } numActions = mPendingActions.size(); @@ -1333,8 +1378,23 @@ final class FragmentManagerImpl extends FragmentManager { mExecutingActions = false; didSomething = true; } + + if (mHavePendingDeferredStart) { + boolean loadersRunning = false; + for (int i=0; i<mActive.size(); i++) { + Fragment f = mActive.get(i); + if (f != null && f.mLoaderManager != null) { + loadersRunning |= f.mLoaderManager.hasRunningLoaders(); + } + } + if (!loadersRunning) { + mHavePendingDeferredStart = false; + startPendingDeferredFragments(); + } + } + return didSomething; } - + void reportBackStackChanged() { if (mBackStackChangeListeners != null) { for (int i=0; i<mBackStackChangeListeners.size(); i++) { @@ -1470,6 +1530,10 @@ final class FragmentManagerImpl extends FragmentManager { result.putSparseParcelableArray( FragmentManagerImpl.VIEW_STATE_TAG, f.mSavedViewState); } + if (!f.mUserVisibleHint) { + // Only add this if it's not the default value + result.putBoolean(FragmentManagerImpl.USER_VISIBLE_HINT_TAG, f.mUserVisibleHint); + } return result; } diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 26813bf..5222d37 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -234,6 +234,7 @@ public interface IActivityManager extends IInterface { public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) throws RemoteException; public void killBackgroundProcesses(final String packageName) throws RemoteException; + public void killAllBackgroundProcesses() throws RemoteException; public void forceStopPackage(final String packageName) throws RemoteException; // Note: probably don't want to allow applications access to these. @@ -605,4 +606,5 @@ public interface IActivityManager extends IInterface { int GET_PROCESS_PSS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+136; int SHOW_BOOT_MESSAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+137; int DISMISS_KEYGUARD_ON_NEXT_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+138; + int KILL_ALL_BACKGROUND_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+139; } diff --git a/core/java/android/app/LoaderManager.java b/core/java/android/app/LoaderManager.java index 89e9ddd..1b8a4f5 100644 --- a/core/java/android/app/LoaderManager.java +++ b/core/java/android/app/LoaderManager.java @@ -418,6 +418,10 @@ class LoaderManagerImpl extends LoaderManager { info.destroy(); mInactiveLoaders.remove(mId); } + + if (mActivity != null && !hasRunningLoaders()) { + mActivity.mFragments.startPendingDeferredFragments(); + } } void callOnLoadFinished(Loader<Object> loader, Object data) { @@ -677,6 +681,9 @@ class LoaderManagerImpl extends LoaderManager { mInactiveLoaders.removeAt(idx); info.destroy(); } + if (mActivity != null && !hasRunningLoaders()) { + mActivity.mFragments.startPendingDeferredFragments(); + } } /** @@ -820,4 +827,14 @@ class LoaderManagerImpl extends LoaderManager { } } } + + public boolean hasRunningLoaders() { + boolean loadersRunning = false; + final int count = mLoaders.size(); + for (int i = 0; i < count; i++) { + final LoaderInfo li = mLoaders.valueAt(i); + loadersRunning |= li.mStarted && !li.mDeliveredData; + } + return loadersRunning; + } } diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index 3290b9d..3aa159e 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -24,6 +24,7 @@ import android.content.DialogInterface; import android.content.Intent; import android.content.pm.ResolveInfo; import android.database.Cursor; +import android.graphics.Rect; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -498,8 +499,24 @@ public class SearchManager ComponentName launchActivity, Bundle appSearchData, boolean globalSearch) { + startSearch(initialQuery, selectInitialQuery, launchActivity, + appSearchData, globalSearch, null); + } + + /** + * As {@link #startSearch(String, boolean, ComponentName, Bundle, boolean)} but including + * source bounds for the global search intent. + * + * @hide + */ + public void startSearch(String initialQuery, + boolean selectInitialQuery, + ComponentName launchActivity, + Bundle appSearchData, + boolean globalSearch, + Rect sourceBounds) { if (globalSearch) { - startGlobalSearch(initialQuery, selectInitialQuery, appSearchData); + startGlobalSearch(initialQuery, selectInitialQuery, appSearchData, sourceBounds); return; } @@ -520,7 +537,7 @@ public class SearchManager * Starts the global search activity. */ /* package */ void startGlobalSearch(String initialQuery, boolean selectInitialQuery, - Bundle appSearchData) { + Bundle appSearchData, Rect sourceBounds) { ComponentName globalSearchActivity = getGlobalSearchActivity(); if (globalSearchActivity == null) { Log.w(TAG, "No global search activity found."); @@ -546,6 +563,7 @@ public class SearchManager if (selectInitialQuery) { intent.putExtra(EXTRA_SELECT_QUERY, selectInitialQuery); } + intent.setSourceBounds(sourceBounds); try { if (DBG) Log.d(TAG, "Starting global search: " + intent.toUri(0)); mContext.startActivity(intent); diff --git a/core/java/android/appwidget/AppWidgetHostView.java b/core/java/android/appwidget/AppWidgetHostView.java index 761c7eb..61a9dce 100644 --- a/core/java/android/appwidget/AppWidgetHostView.java +++ b/core/java/android/appwidget/AppWidgetHostView.java @@ -26,6 +26,7 @@ import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.Paint; +import android.graphics.Rect; import android.os.Build; import android.os.Parcel; import android.os.Parcelable; @@ -41,8 +42,8 @@ import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.FrameLayout; import android.widget.RemoteViews; -import android.widget.TextView; import android.widget.RemoteViewsAdapter.RemoteAdapterConnectionCallback; +import android.widget.TextView; /** * Provides the glue to show AppWidget views. This class offers automatic animation @@ -106,7 +107,9 @@ public class AppWidgetHostView extends FrameLayout { } /** - * Set the AppWidget that will be displayed by this view. + * Set the AppWidget that will be displayed by this view. This method also adds default padding + * to widgets, as described in {@link #getDefaultPaddingForWidget(Context, ComponentName, Rect)} + * and can be overridden in order to add custom padding. */ public void setAppWidget(int appWidgetId, AppWidgetProviderInfo info) { mAppWidgetId = appWidgetId; @@ -116,49 +119,57 @@ public class AppWidgetHostView extends FrameLayout { // a widget, eg. for some widgets in safe mode. if (info != null) { // We add padding to the AppWidgetHostView if necessary - Padding padding = getPaddingForWidget(info.provider); + Rect padding = getDefaultPaddingForWidget(mContext, info.provider, null); setPadding(padding.left, padding.top, padding.right, padding.bottom); } } - private static class Padding { - int left = 0; - int right = 0; - int top = 0; - int bottom = 0; - } - /** * As of ICE_CREAM_SANDWICH we are automatically adding padding to widgets targeting * ICE_CREAM_SANDWICH and higher. The new widget design guidelines strongly recommend * that widget developers do not add extra padding to their widgets. This will help * achieve consistency among widgets. + * + * Note: this method is only needed by developers of AppWidgetHosts. The method is provided in + * order for the AppWidgetHost to account for the automatic padding when computing the number + * of cells to allocate to a particular widget. + * + * @param context the current context + * @param component the component name of the widget + * @param padding Rect in which to place the output, if null, a new Rect will be allocated and + * returned + * @return default padding for this widget */ - private Padding getPaddingForWidget(ComponentName component) { - PackageManager packageManager = mContext.getPackageManager(); - Padding p = new Padding(); + public static Rect getDefaultPaddingForWidget(Context context, ComponentName component, + Rect padding) { + PackageManager packageManager = context.getPackageManager(); ApplicationInfo appInfo; + if (padding == null) { + padding = new Rect(0, 0, 0, 0); + } else { + padding.set(0, 0, 0, 0); + } + try { appInfo = packageManager.getApplicationInfo(component.getPackageName(), 0); - } catch (Exception e) { + } catch (NameNotFoundException e) { // if we can't find the package, return 0 padding - return p; + return padding; } if (appInfo.targetSdkVersion >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - Resources r = getResources(); - p.left = r.getDimensionPixelSize(com.android.internal. + Resources r = context.getResources(); + padding.left = r.getDimensionPixelSize(com.android.internal. R.dimen.default_app_widget_padding_left); - p.right = r.getDimensionPixelSize(com.android.internal. + padding.right = r.getDimensionPixelSize(com.android.internal. R.dimen.default_app_widget_padding_right); - p.top = r.getDimensionPixelSize(com.android.internal. + padding.top = r.getDimensionPixelSize(com.android.internal. R.dimen.default_app_widget_padding_top); - p.bottom = r.getDimensionPixelSize(com.android.internal. + padding.bottom = r.getDimensionPixelSize(com.android.internal. R.dimen.default_app_widget_padding_bottom); } - - return p; + return padding; } public int getAppWidgetId() { diff --git a/core/java/android/bluetooth/BluetoothDevice.java b/core/java/android/bluetooth/BluetoothDevice.java index 4cb8220..0306521 100644 --- a/core/java/android/bluetooth/BluetoothDevice.java +++ b/core/java/android/bluetooth/BluetoothDevice.java @@ -247,13 +247,12 @@ public final class BluetoothDevice implements Parcelable { * has been fetched. This intent is sent only when the UUIDs of the remote * device are requested to be fetched using Service Discovery Protocol * <p> Always contains the extra field {@link #EXTRA_DEVICE} - * <p> Always contains the extra filed {@link #EXTRA_UUID} + * <p> Always contains the extra field {@link #EXTRA_UUID} * <p>Requires {@link android.Manifest.permission#BLUETOOTH} to receive. - * @hide */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_UUID = - "android.bleutooth.device.action.UUID"; + "android.bluetooth.device.action.UUID"; /** * Broadcast Action: Indicates a failure to retrieve the name of a remote @@ -451,7 +450,6 @@ public final class BluetoothDevice implements Parcelable { * Used as an extra field in {@link #ACTION_UUID} intents, * Contains the {@link android.os.ParcelUuid}s of the remote device which * is a parcelable version of {@link UUID}. - * @hide */ public static final String EXTRA_UUID = "android.bluetooth.device.extra.UUID"; @@ -770,7 +768,18 @@ public final class BluetoothDevice implements Parcelable { return false; } - /** @hide */ + /** + * Returns the supported features (UUIDs) of the remote device. + * + * <p>This method does not start a service discovery procedure to retrieve the UUIDs + * from the remote device. Instead, the local cached copy of the service + * UUIDs are returned. + * <p>Use {@link #fetchUuidsWithSdp} if fresh UUIDs are desired. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. + * + * @return the supported features (UUIDs) of the remote device, + * or null on error + */ public ParcelUuid[] getUuids() { try { return sService.getRemoteUuids(mAddress); @@ -779,18 +788,19 @@ public final class BluetoothDevice implements Parcelable { } /** - * Perform a SDP query on the remote device to get the UUIDs - * supported. This API is asynchronous and an Intent is sent, - * with the UUIDs supported by the remote end. If there is an error - * in getting the SDP records or if the process takes a long time, - * an Intent is sent with the UUIDs that is currently present in the - * cache. Clients should use the {@link #getUuids} to get UUIDs - * is SDP is not to be performed. + * Perform a service discovery on the remote device to get the UUIDs supported. + * + * <p>This API is asynchronous and {@link #ACTION_UUID} intent is sent, + * with the UUIDs supported by the remote end. If there is an error + * in getting the SDP records or if the process takes a long time, + * {@link #ACTION_UUID} intent is sent with the UUIDs that is currently + * present in the cache. Clients should use the {@link #getUuids} to get UUIDs + * if service discovery is not to be performed. + * <p>Requires {@link android.Manifest.permission#BLUETOOTH}. * - * @return False if the sanity check fails, True if the process + * @return False if the sanity check fails, True if the process * of initiating an ACL connection to the remote device * was started. - * @hide */ public boolean fetchUuidsWithSdp() { try { diff --git a/core/java/android/content/ContentResolver.java b/core/java/android/content/ContentResolver.java index e923349..cc3219b 100644 --- a/core/java/android/content/ContentResolver.java +++ b/core/java/android/content/ContentResolver.java @@ -26,6 +26,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.AssetFileDescriptor; import android.content.res.Resources; import android.database.ContentObserver; +import android.database.CrossProcessCursorWrapper; import android.database.Cursor; import android.database.CursorWrapper; import android.database.IContentObserver; @@ -1568,7 +1569,7 @@ public abstract class ContentResolver { samplePercent); } - private final class CursorWrapperInner extends CursorWrapper { + private final class CursorWrapperInner extends CrossProcessCursorWrapper { private final IContentProvider mContentProvider; public static final String TAG="CursorWrapperInner"; diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java index 9468581..bfbd0ac 100644 --- a/core/java/android/content/Context.java +++ b/core/java/android/content/Context.java @@ -185,7 +185,14 @@ public abstract class Context { * used to reduce the amount that the client process's overall importance * is used to impact it. */ - public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0040; + public static final int BIND_ADJUST_WITH_ACTIVITY = 0x0080; + + /** + * Flag for {@link #bindService}: Don't consider the bound service to be + * visible, even if the caller is visible. + * @hide + */ + public static final int BIND_NOT_VISIBLE = 0x40000000; /** Return an AssetManager instance for your application's package. */ public abstract AssetManager getAssets(); diff --git a/core/java/android/content/Loader.java b/core/java/android/content/Loader.java index b962800..2d2a90d 100644 --- a/core/java/android/content/Loader.java +++ b/core/java/android/content/Loader.java @@ -183,6 +183,12 @@ public class Loader<D> { } /** + * This function will normally be called for you automatically by + * {@link android.app.LoaderManager} when the associated fragment/activity + * is being started. When using a Loader with {@link android.app.LoaderManager}, + * you <em>must not</em> call this method yourself, or you will conflict + * with its management of the Loader. + * * Starts an asynchronous load of the Loader's data. When the result * is ready the callbacks will be called on the process's main thread. * If a previous load has been completed and is still valid @@ -232,7 +238,13 @@ public class Loader<D> { } /** - * Stops delivery of updates until the next time {@link #startLoading()} is called. + * This function will normally be called for you automatically by + * {@link android.app.LoaderManager} when the associated fragment/activity + * is being stopped. When using a Loader with {@link android.app.LoaderManager}, + * you <em>must not</em> call this method yourself, or you will conflict + * with its management of the Loader. + * + * <p>Stops delivery of updates until the next time {@link #startLoading()} is called. * Implementations should <em>not</em> invalidate their data at this point -- * clients are still free to use the last data the loader reported. They will, * however, typically stop reporting new data if the data changes; they can @@ -260,6 +272,12 @@ public class Loader<D> { } /** + * This function will normally be called for you automatically by + * {@link android.app.LoaderManager} when restarting a Loader. When using + * a Loader with {@link android.app.LoaderManager}, + * you <em>must not</em> call this method yourself, or you will conflict + * with its management of the Loader. + * * Tell the Loader that it is being abandoned. This is called prior * to {@link #reset} to have it retain its current data but not report * any new data. @@ -282,6 +300,12 @@ public class Loader<D> { } /** + * This function will normally be called for you automatically by + * {@link android.app.LoaderManager} when destroying a Loader. When using + * a Loader with {@link android.app.LoaderManager}, + * you <em>must not</em> call this method yourself, or you will conflict + * with its management of the Loader. + * * Resets the state of the Loader. The Loader should at this point free * all of its resources, since it may never be called again; however, its * {@link #startLoading()} may later be called at which point it must be diff --git a/core/java/android/content/SyncManager.java b/core/java/android/content/SyncManager.java index 7d683a5..b2909b3 100644 --- a/core/java/android/content/SyncManager.java +++ b/core/java/android/content/SyncManager.java @@ -416,7 +416,8 @@ public class SyncManager implements OnAccountsUpdateListener { intent.setComponent(syncAdapterInfo.componentName); if (!mContext.bindService(intent, new InitializerServiceConnection(account, authority, mContext, mMainHandler), - Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND)) { + Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND + | Context.BIND_ALLOW_OOM_MANAGEMENT)) { Log.w(TAG, "initializeSyncAdapter: failed to bind to " + intent); } } @@ -971,7 +972,8 @@ public class SyncManager implements OnAccountsUpdateListener { mContext, 0, new Intent(Settings.ACTION_SYNC_SETTINGS), 0)); mBound = true; final boolean bindResult = mContext.bindService(intent, this, - Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND); + Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND + | Context.BIND_ALLOW_OOM_MANAGEMENT); if (!bindResult) { mBound = false; } diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java index 3eb7647..8541748 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -153,10 +153,14 @@ public abstract class PackageManager { public static final int GET_PERMISSIONS = 0x00001000; /** - * Flag parameter to retrieve all applications(even uninstalled ones) with data directories. - * This state could have resulted if applications have been deleted with flag - * DONT_DELETE_DATA - * with a possibility of being replaced or reinstalled in future + * Flag parameter to retrieve some information about all applications (even + * uninstalled ones) which have data directories. This state could have + * resulted if applications have been deleted with flag + * {@code DONT_DELETE_DATA} with a possibility of being replaced or + * reinstalled in future. + * <p> + * Note: this flag may cause less information about currently installed + * applications to be returned. */ public static final int GET_UNINSTALLED_PACKAGES = 0x00002000; diff --git a/core/java/android/database/AbstractCursor.java b/core/java/android/database/AbstractCursor.java index ee6aec6..74fef29 100644 --- a/core/java/android/database/AbstractCursor.java +++ b/core/java/android/database/AbstractCursor.java @@ -53,7 +53,10 @@ public abstract class AbstractCursor implements CrossProcessCursor { abstract public boolean isNull(int column); public int getType(int column) { - throw new UnsupportedOperationException(); + // Reflects the assumption that all commonly used field types (meaning everything + // but blobs) are convertible to strings so it should be safe to call + // getString to retrieve them. + return FIELD_TYPE_STRING; } // TODO implement getBlob in all cursor types @@ -185,46 +188,9 @@ public abstract class AbstractCursor implements CrossProcessCursor { return result; } - /** - * Copy data from cursor to CursorWindow - * @param position start position of data - * @param window - */ + @Override public void fillWindow(int position, CursorWindow window) { - if (position < 0 || position >= getCount()) { - return; - } - window.acquireReference(); - try { - int oldpos = mPos; - mPos = position - 1; - window.clear(); - window.setStartPosition(position); - int columnNum = getColumnCount(); - window.setNumColumns(columnNum); - while (moveToNext() && window.allocRow()) { - for (int i = 0; i < columnNum; i++) { - String field = getString(i); - if (field != null) { - if (!window.putString(field, mPos, i)) { - window.freeLastRow(); - break; - } - } else { - if (!window.putNull(mPos, i)) { - window.freeLastRow(); - break; - } - } - } - } - - mPos = oldpos; - } catch (IllegalStateException e){ - // simply ignore it - } finally { - window.releaseReference(); - } + DatabaseUtils.cursorFillWindow(this, position, window); } public final boolean move(int offset) { diff --git a/core/java/android/database/AbstractWindowedCursor.java b/core/java/android/database/AbstractWindowedCursor.java index d0aedd2..083485f 100644 --- a/core/java/android/database/AbstractWindowedCursor.java +++ b/core/java/android/database/AbstractWindowedCursor.java @@ -188,15 +188,14 @@ public abstract class AbstractWindowedCursor extends AbstractCursor { /** * If there is a window, clear it. - * Otherwise, creates a local window. + * Otherwise, creates a new window. * * @param name The window name. * @hide */ - protected void clearOrCreateLocalWindow(String name) { + protected void clearOrCreateWindow(String name) { if (mWindow == null) { - // If there isn't a window set already it will only be accessed locally - mWindow = new CursorWindow(name, true /* the window is local only */); + mWindow = new CursorWindow(name); } else { mWindow.clear(); } diff --git a/core/java/android/database/CrossProcessCursor.java b/core/java/android/database/CrossProcessCursor.java index 8e6a5aa..26379cc 100644 --- a/core/java/android/database/CrossProcessCursor.java +++ b/core/java/android/database/CrossProcessCursor.java @@ -16,27 +16,63 @@ package android.database; +/** + * A cross process cursor is an extension of a {@link Cursor} that also supports + * usage from remote processes. + * <p> + * The contents of a cross process cursor are marshalled to the remote process by + * filling {@link CursorWindow} objects using {@link #fillWindow}. As an optimization, + * the cursor can provide a pre-filled window to use via {@link #getWindow} thereby + * obviating the need to copy the data to yet another cursor window. + */ public interface CrossProcessCursor extends Cursor { /** - * returns a pre-filled window, return NULL if no such window + * Returns a pre-filled window that contains the data within this cursor. + * <p> + * In particular, the window contains the row indicated by {@link Cursor#getPosition}. + * The window's contents are automatically scrolled whenever the current + * row moved outside the range covered by the window. + * </p> + * + * @return The pre-filled window, or null if none. */ CursorWindow getWindow(); /** - * copies cursor data into the window start at pos + * Copies cursor data into the window. + * <p> + * Clears the window and fills it with data beginning at the requested + * row position until all of the data in the cursor is exhausted + * or the window runs out of space. + * </p><p> + * The filled window uses the same row indices as the original cursor. + * For example, if you fill a window starting from row 5 from the cursor, + * you can query the contents of row 5 from the window just by asking it + * for row 5 because there is a direct correspondence between the row indices + * used by the cursor and the window. + * </p><p> + * The current position of the cursor, as returned by {@link #getPosition}, + * is not changed by this method. + * </p> + * + * @param position The zero-based index of the first row to copy into the window. + * @param window The window to fill. */ - void fillWindow(int pos, CursorWindow winow); + void fillWindow(int position, CursorWindow window); /** * This function is called every time the cursor is successfully scrolled * to a new position, giving the subclass a chance to update any state it - * may have. If it returns false the move function will also do so and the + * may have. If it returns false the move function will also do so and the * cursor will scroll to the beforeFirst position. + * <p> + * This function should be called by methods such as {@link #moveToPosition(int)}, + * so it will typically not be called from outside of the cursor class itself. + * </p> * - * @param oldPosition the position that we're moving from - * @param newPosition the position that we're moving to - * @return true if the move is successful, false otherwise + * @param oldPosition The position that we're moving from. + * @param newPosition The position that we're moving to. + * @return True if the move is successful, false otherwise. */ boolean onMove(int oldPosition, int newPosition); - } diff --git a/core/java/android/database/CrossProcessCursorWrapper.java b/core/java/android/database/CrossProcessCursorWrapper.java new file mode 100644 index 0000000..8c250b8 --- /dev/null +++ b/core/java/android/database/CrossProcessCursorWrapper.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2011 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.database; + +import android.database.CrossProcessCursor; +import android.database.Cursor; +import android.database.CursorWindow; +import android.database.CursorWrapper; + +/** + * Cursor wrapper that implements {@link CrossProcessCursor}. + * <p> + * If the wrapper cursor implemented {@link CrossProcessCursor}, then delegates + * {@link #fillWindow}, {@link #getWindow()} and {@link #onMove} to it. Otherwise, + * provides default implementations of these methods that traverse the contents + * of the cursor similar to {@link AbstractCursor#fillWindow}. + * </p><p> + * This wrapper can be used to adapt an ordinary {@link Cursor} into a + * {@link CrossProcessCursor}. + * </p> + */ +public class CrossProcessCursorWrapper extends CursorWrapper implements CrossProcessCursor { + /** + * Creates a cross process cursor wrapper. + * @param cursor The underlying cursor to wrap. + */ + public CrossProcessCursorWrapper(Cursor cursor) { + super(cursor); + } + + @Override + public void fillWindow(int position, CursorWindow window) { + if (mCursor instanceof CrossProcessCursor) { + final CrossProcessCursor crossProcessCursor = (CrossProcessCursor)mCursor; + crossProcessCursor.fillWindow(position, window); + return; + } + + DatabaseUtils.cursorFillWindow(mCursor, position, window); + } + + @Override + public CursorWindow getWindow() { + if (mCursor instanceof CrossProcessCursor) { + final CrossProcessCursor crossProcessCursor = (CrossProcessCursor)mCursor; + return crossProcessCursor.getWindow(); + } + + return null; + } + + @Override + public boolean onMove(int oldPosition, int newPosition) { + if (mCursor instanceof CrossProcessCursor) { + final CrossProcessCursor crossProcessCursor = (CrossProcessCursor)mCursor; + return crossProcessCursor.onMove(oldPosition, newPosition); + } + + return true; + } +} diff --git a/core/java/android/database/CursorToBulkCursorAdaptor.java b/core/java/android/database/CursorToBulkCursorAdaptor.java index dd2c9b7..215035d 100644 --- a/core/java/android/database/CursorToBulkCursorAdaptor.java +++ b/core/java/android/database/CursorToBulkCursorAdaptor.java @@ -25,9 +25,9 @@ import android.util.Log; /** * Wraps a BulkCursor around an existing Cursor making it remotable. * <p> - * If the wrapped cursor is a {@link AbstractWindowedCursor} then it owns - * the cursor window. Otherwise, the adaptor takes ownership of the - * cursor itself and ensures it gets closed as needed during deactivation + * If the wrapped cursor returns non-null from {@link CrossProcessCursor#getWindow} + * then it is assumed to own the window. Otherwise, the adaptor provides a + * window to be filled and ensures it gets closed as needed during deactivation * and requeries. * </p> * @@ -48,12 +48,11 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative private CrossProcessCursor mCursor; /** - * The cursor window used by the cross process cursor. - * This field is always null for abstract windowed cursors since they are responsible - * for managing the lifetime of their window. + * The cursor window that was filled by the cross process cursor in the + * case where the cursor does not support getWindow. + * This field is only ever non-null when the window has actually be filled. */ - private CursorWindow mWindowForNonWindowedCursor; - private boolean mWindowForNonWindowedCursorWasFilled; + private CursorWindow mFilledWindow; private static final class ContentObserverProxy extends ContentObserver { protected IContentObserver mRemote; @@ -90,11 +89,10 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative public CursorToBulkCursorAdaptor(Cursor cursor, IContentObserver observer, String providerName) { - try { - mCursor = (CrossProcessCursor) cursor; - } catch (ClassCastException e) { - throw new UnsupportedOperationException( - "Only CrossProcessCursor cursors are supported across process for now", e); + if (cursor instanceof CrossProcessCursor) { + mCursor = (CrossProcessCursor)cursor; + } else { + mCursor = new CrossProcessCursorWrapper(cursor); } mProviderName = providerName; @@ -103,11 +101,10 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative } } - private void closeWindowForNonWindowedCursorLocked() { - if (mWindowForNonWindowedCursor != null) { - mWindowForNonWindowedCursor.close(); - mWindowForNonWindowedCursor = null; - mWindowForNonWindowedCursorWasFilled = false; + private void closeFilledWindowLocked() { + if (mFilledWindow != null) { + mFilledWindow.close(); + mFilledWindow = null; } } @@ -118,7 +115,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative mCursor = null; } - closeWindowForNonWindowedCursorLocked(); + closeFilledWindowLocked(); } private void throwIfCursorIsClosed() { @@ -139,30 +136,24 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative synchronized (mLock) { throwIfCursorIsClosed(); - CursorWindow window; - if (mCursor instanceof AbstractWindowedCursor) { - AbstractWindowedCursor windowedCursor = (AbstractWindowedCursor)mCursor; - window = windowedCursor.getWindow(); - if (window == null) { - window = new CursorWindow(mProviderName, false /*localOnly*/); - windowedCursor.setWindow(window); - } + if (!mCursor.moveToPosition(startPos)) { + closeFilledWindowLocked(); + return null; + } - mCursor.moveToPosition(startPos); + CursorWindow window = mCursor.getWindow(); + if (window != null) { + closeFilledWindowLocked(); } else { - window = mWindowForNonWindowedCursor; + window = mFilledWindow; if (window == null) { - window = new CursorWindow(mProviderName, false /*localOnly*/); - mWindowForNonWindowedCursor = window; - } - - mCursor.moveToPosition(startPos); - - if (!mWindowForNonWindowedCursorWasFilled - || startPos < window.getStartPosition() + mFilledWindow = new CursorWindow(mProviderName); + window = mFilledWindow; + mCursor.fillWindow(startPos, window); + } else if (startPos < window.getStartPosition() || startPos >= window.getStartPosition() + window.getNumRows()) { + window.clear(); mCursor.fillWindow(startPos, window); - mWindowForNonWindowedCursorWasFilled = true; } } @@ -211,7 +202,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative mCursor.deactivate(); } - closeWindowForNonWindowedCursorLocked(); + closeFilledWindowLocked(); } } @@ -227,7 +218,7 @@ public final class CursorToBulkCursorAdaptor extends BulkCursorNative synchronized (mLock) { throwIfCursorIsClosed(); - closeWindowForNonWindowedCursorLocked(); + closeFilledWindowLocked(); try { if (!mCursor.requery()) { diff --git a/core/java/android/database/CursorWindow.java b/core/java/android/database/CursorWindow.java index a1be121..e9675e8 100644 --- a/core/java/android/database/CursorWindow.java +++ b/core/java/android/database/CursorWindow.java @@ -31,8 +31,8 @@ import android.util.SparseIntArray; /** * A buffer containing multiple cursor rows. * <p> - * A {@link CursorWindow} is read-write when created and used locally. When sent - * to a remote process (by writing it to a {@link Parcel}), the remote process + * A {@link CursorWindow} is read-write when initially created and used locally. + * When sent to a remote process (by writing it to a {@link Parcel}), the remote process * receives a read-only view of the cursor window. Typically the cursor window * will be allocated by the producer, filled with data, and then sent to the * consumer for reading. @@ -59,8 +59,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { private final CloseGuard mCloseGuard = CloseGuard.get(); - private static native int nativeCreate(String name, - int cursorWindowSize, boolean localOnly); + private static native int nativeCreate(String name, int cursorWindowSize); private static native int nativeCreateFromParcel(Parcel parcel); private static native void nativeDispose(int windowPtr); private static native void nativeWriteToParcel(int windowPtr, Parcel parcel); @@ -96,15 +95,11 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * </p> * * @param name The name of the cursor window, or null if none. - * @param localWindow True if this window will be used in this process only, - * false if it might be sent to another processes. - * - * @hide */ - public CursorWindow(String name, boolean localWindow) { + public CursorWindow(String name) { mStartPos = 0; mName = name; - mWindowPtr = nativeCreate(name, sCursorWindowSize, localWindow); + mWindowPtr = nativeCreate(name, sCursorWindowSize); if (mWindowPtr == 0) { throw new CursorWindowAllocationException("Cursor window allocation of " + (sCursorWindowSize / 1024) + " kb failed. " + printStats()); @@ -121,10 +116,14 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * </p> * * @param localWindow True if this window will be used in this process only, - * false if it might be sent to another processes. + * false if it might be sent to another processes. This argument is ignored. + * + * @deprecated There is no longer a distinction between local and remote + * cursor windows. Use the {@link #CursorWindow(String)} constructor instead. */ + @Deprecated public CursorWindow(boolean localWindow) { - this(null, localWindow); + this((String)null); } private CursorWindow(Parcel source) { @@ -285,8 +284,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * Returns true if the field at the specified row and column index * has type {@link Cursor#FIELD_TYPE_NULL}. * - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return True if the field has type {@link Cursor#FIELD_TYPE_NULL}. * @deprecated Use {@link #getType(int, int)} instead. @@ -300,8 +298,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * Returns true if the field at the specified row and column index * has type {@link Cursor#FIELD_TYPE_BLOB} or {@link Cursor#FIELD_TYPE_NULL}. * - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return True if the field has type {@link Cursor#FIELD_TYPE_BLOB} or * {@link Cursor#FIELD_TYPE_NULL}. @@ -317,8 +314,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * Returns true if the field at the specified row and column index * has type {@link Cursor#FIELD_TYPE_INTEGER}. * - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return True if the field has type {@link Cursor#FIELD_TYPE_INTEGER}. * @deprecated Use {@link #getType(int, int)} instead. @@ -332,8 +328,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * Returns true if the field at the specified row and column index * has type {@link Cursor#FIELD_TYPE_FLOAT}. * - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return True if the field has type {@link Cursor#FIELD_TYPE_FLOAT}. * @deprecated Use {@link #getType(int, int)} instead. @@ -347,8 +342,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * Returns true if the field at the specified row and column index * has type {@link Cursor#FIELD_TYPE_STRING} or {@link Cursor#FIELD_TYPE_NULL}. * - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return True if the field has type {@link Cursor#FIELD_TYPE_STRING} * or {@link Cursor#FIELD_TYPE_NULL}. @@ -373,8 +367,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * </ul> * </p> * - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return The field type. */ @@ -404,8 +397,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * </ul> * </p> * - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return The value of the field as a byte array. */ @@ -440,8 +432,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * </ul> * </p> * - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return The value of the field as a string. */ @@ -479,8 +470,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * </ul> * </p> * - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @param buffer The {@link CharArrayBuffer} to hold the string. It is automatically * resized if the requested string is larger than the buffer's current capacity. @@ -515,8 +505,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * </ul> * </p> * - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return The value of the field as a <code>long</code>. */ @@ -548,8 +537,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * </ul> * </p> * - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return The value of the field as a <code>double</code>. */ @@ -570,8 +558,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * result to <code>short</code>. * </p> * - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return The value of the field as a <code>short</code>. */ @@ -587,8 +574,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * result to <code>int</code>. * </p> * - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return The value of the field as an <code>int</code>. */ @@ -604,8 +590,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * result to <code>float</code>. * </p> * - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return The value of the field as an <code>float</code>. */ @@ -617,8 +602,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * Copies a byte array into the field at the specified row and column index. * * @param value The value to store. - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return True if successful. */ @@ -635,8 +619,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * Copies a string into the field at the specified row and column index. * * @param value The value to store. - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return True if successful. */ @@ -653,8 +636,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * Puts a long integer into the field at the specified row and column index. * * @param value The value to store. - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return True if successful. */ @@ -672,8 +654,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { * specified row and column index. * * @param value The value to store. - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return True if successful. */ @@ -689,8 +670,7 @@ public class CursorWindow extends SQLiteClosable implements Parcelable { /** * Puts a null value into the field at the specified row and column index. * - * @param row The zero-based row index, relative to the cursor window's - * start position ({@link #getStartPosition()}). + * @param row The zero-based row index. * @param column The zero-based column index. * @return True if successful. */ diff --git a/core/java/android/database/CursorWrapper.java b/core/java/android/database/CursorWrapper.java index 320733e..7baeb8c 100644 --- a/core/java/android/database/CursorWrapper.java +++ b/core/java/android/database/CursorWrapper.java @@ -25,9 +25,13 @@ import android.os.Bundle; * use for this class is to extend a cursor while overriding only a subset of its methods. */ public class CursorWrapper implements Cursor { + /** @hide */ + protected final Cursor mCursor; - private final Cursor mCursor; - + /** + * Creates a cursor wrapper. + * @param cursor The underlying cursor to wrap. + */ public CursorWrapper(Cursor cursor) { mCursor = cursor; } diff --git a/core/java/android/database/DatabaseUtils.java b/core/java/android/database/DatabaseUtils.java index 8e6f699..a10ca15 100644 --- a/core/java/android/database/DatabaseUtils.java +++ b/core/java/android/database/DatabaseUtils.java @@ -237,7 +237,8 @@ public class DatabaseUtils { return Cursor.FIELD_TYPE_BLOB; } else if (obj instanceof Float || obj instanceof Double) { return Cursor.FIELD_TYPE_FLOAT; - } else if (obj instanceof Long || obj instanceof Integer) { + } else if (obj instanceof Long || obj instanceof Integer + || obj instanceof Short || obj instanceof Byte) { return Cursor.FIELD_TYPE_INTEGER; } else { return Cursor.FIELD_TYPE_STRING; @@ -245,6 +246,82 @@ public class DatabaseUtils { } /** + * Fills the specified cursor window by iterating over the contents of the cursor. + * The window is filled until the cursor is exhausted or the window runs out + * of space. + * + * The original position of the cursor is left unchanged by this operation. + * + * @param cursor The cursor that contains the data to put in the window. + * @param position The start position for filling the window. + * @param window The window to fill. + * @hide + */ + public static void cursorFillWindow(final Cursor cursor, + int position, final CursorWindow window) { + if (position < 0 || position >= cursor.getCount()) { + return; + } + window.acquireReference(); + try { + final int oldPos = cursor.getPosition(); + final int numColumns = cursor.getColumnCount(); + window.clear(); + window.setStartPosition(position); + window.setNumColumns(numColumns); + if (cursor.moveToPosition(position)) { + do { + if (!window.allocRow()) { + break; + } + for (int i = 0; i < numColumns; i++) { + final int type = cursor.getType(i); + final boolean success; + switch (type) { + case Cursor.FIELD_TYPE_NULL: + success = window.putNull(position, i); + break; + + case Cursor.FIELD_TYPE_INTEGER: + success = window.putLong(cursor.getLong(i), position, i); + break; + + case Cursor.FIELD_TYPE_FLOAT: + success = window.putDouble(cursor.getDouble(i), position, i); + break; + + case Cursor.FIELD_TYPE_BLOB: { + final byte[] value = cursor.getBlob(i); + success = value != null ? window.putBlob(value, position, i) + : window.putNull(position, i); + break; + } + + default: // assume value is convertible to String + case Cursor.FIELD_TYPE_STRING: { + final String value = cursor.getString(i); + success = value != null ? window.putString(value, position, i) + : window.putNull(position, i); + break; + } + } + if (!success) { + window.freeLastRow(); + break; + } + } + position += 1; + } while (cursor.moveToNext()); + } + cursor.moveToPosition(oldPos); + } catch (IllegalStateException e){ + // simply ignore it + } finally { + window.releaseReference(); + } + } + + /** * Appends an SQL string to the given StringBuilder, including the opening * and closing single quotes. Any single quotes internal to sqlString will * be escaped. diff --git a/core/java/android/database/sqlite/SQLiteCompiledSql.java b/core/java/android/database/sqlite/SQLiteCompiledSql.java index bdb96b1..dafbc79 100644 --- a/core/java/android/database/sqlite/SQLiteCompiledSql.java +++ b/core/java/android/database/sqlite/SQLiteCompiledSql.java @@ -49,7 +49,7 @@ import android.util.Log; /** the following are for debugging purposes */ private String mSqlStmt = null; - private Throwable mStackTrace = null; + private final Throwable mStackTrace; /** when in cache and is in use, this member is set */ private boolean mInUse = false; @@ -59,7 +59,11 @@ import android.util.Log; db.verifyLockOwner(); mDatabase = db; mSqlStmt = sql; - mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace(); + if (StrictMode.vmSqliteObjectLeaksEnabled()) { + mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace(); + } else { + mStackTrace = null; + } nHandle = db.mNativeHandle; native_compile(sql); } @@ -112,7 +116,7 @@ import android.util.Log; // but if the database itself is not closed and is GC'ed, then // all sub-objects attached to the database could end up getting GC'ed too. // in that case, don't print any warning. - if (mInUse && StrictMode.vmSqliteObjectLeaksEnabled()) { + if (mInUse && mStackTrace != null) { int len = mSqlStmt.length(); StrictMode.onSqliteObjectLeaked( "Releasing statement in a finalizer. Please ensure " + diff --git a/core/java/android/database/sqlite/SQLiteCursor.java b/core/java/android/database/sqlite/SQLiteCursor.java index a1c36e2..c24acd4 100644 --- a/core/java/android/database/sqlite/SQLiteCursor.java +++ b/core/java/android/database/sqlite/SQLiteCursor.java @@ -95,7 +95,11 @@ public class SQLiteCursor extends AbstractWindowedCursor { if (query.mDatabase == null) { throw new IllegalArgumentException("query.mDatabase cannot be null"); } - mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace(); + if (StrictMode.vmSqliteObjectLeaksEnabled()) { + mStackTrace = new DatabaseObjectNotClosedException().fillInStackTrace(); + } else { + mStackTrace = null; + } mDriver = driver; mEditTable = editTable; mColumnNameMap = null; @@ -155,7 +159,7 @@ public class SQLiteCursor extends AbstractWindowedCursor { } private void fillWindow(int startPos) { - clearOrCreateLocalWindow(getDatabase().getPath()); + clearOrCreateWindow(getDatabase().getPath()); mWindow.setStartPosition(startPos); int count = getQuery().fillWindow(mWindow); if (startPos == 0) { // fillWindow returns count(*) only for startPos = 0 @@ -319,7 +323,7 @@ public class SQLiteCursor extends AbstractWindowedCursor { try { // if the cursor hasn't been closed yet, close it first if (mWindow != null) { - if (StrictMode.vmSqliteObjectLeaksEnabled()) { + if (mStackTrace != null) { int len = mQuery.mSql.length(); StrictMode.onSqliteObjectLeaked( "Finalizing a Cursor that has not been deactivated or closed. " + diff --git a/core/java/android/database/sqlite/package.html b/core/java/android/database/sqlite/package.html index ff0f9f5..ceed171 100644 --- a/core/java/android/database/sqlite/package.html +++ b/core/java/android/database/sqlite/package.html @@ -3,7 +3,7 @@ Contains the SQLite database management classes that an application would use to manage its own private database. <p> -Applications use these classes to maange private databases. If creating a +Applications use these classes to manage private databases. If creating a content provider, you will probably have to use these classes to create and manage your own database to store content. See <a href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a> to learn diff --git a/core/java/android/hardware/Camera.java b/core/java/android/hardware/Camera.java index 68f0247..48adfad 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -3259,7 +3259,6 @@ public class Camera { * disable video stabilization. * @see #isVideoStabilizationSupported() * @see #getVideoStabilization() - * @hide */ public void setVideoStabilization(boolean toggle) { set(KEY_VIDEO_STABILIZATION, toggle ? TRUE : FALSE); @@ -3272,7 +3271,6 @@ public class Camera { * @return true if video stabilization is enabled * @see #isVideoStabilizationSupported() * @see #setVideoStabilization(boolean) - * @hide */ public boolean getVideoStabilization() { String str = get(KEY_VIDEO_STABILIZATION); @@ -3286,7 +3284,6 @@ public class Camera { * @return true if video stabilization is supported * @see #setVideoStabilization(boolean) * @see #getVideoStabilization() - * @hide */ public boolean isVideoStabilizationSupported() { String str = get(KEY_VIDEO_STABILIZATION_SUPPORTED); diff --git a/core/java/android/inputmethodservice/KeyboardView.java b/core/java/android/inputmethodservice/KeyboardView.java index 5343e2a..5143f7f 100644 --- a/core/java/android/inputmethodservice/KeyboardView.java +++ b/core/java/android/inputmethodservice/KeyboardView.java @@ -1145,44 +1145,29 @@ public class KeyboardView extends View implements View.OnClickListener { @Override public boolean onHoverEvent(MotionEvent event) { - // If touch exploring is enabled we ignore touch events and transform - // the stream of hover events as touch events. This allows one consistent - // event stream to drive the keyboard since during touch exploring the - // first touch generates only hover events and tapping on the same - // location generates hover and touch events. if (mAccessibilityManager.isTouchExplorationEnabled() && event.getPointerCount() == 1) { final int action = event.getAction(); switch (action) { case MotionEvent.ACTION_HOVER_ENTER: - event.setAction(MotionEvent.ACTION_DOWN); - break; case MotionEvent.ACTION_HOVER_MOVE: - event.setAction(MotionEvent.ACTION_MOVE); + final int touchX = (int) event.getX() - mPaddingLeft; + int touchY = (int) event.getY() - mPaddingTop; + if (touchY >= -mVerticalCorrection) { + touchY += mVerticalCorrection; + } + final int keyIndex = getKeyIndices(touchX, touchY, null); + showPreview(keyIndex); break; case MotionEvent.ACTION_HOVER_EXIT: - event.setAction(MotionEvent.ACTION_UP); + showPreview(NOT_A_KEY); break; } - onTouchEventInternal(event); - event.setAction(action); } - return super.onHoverEvent(event); + return true; } @Override - public boolean onTouchEvent(MotionEvent event) { - // If touch exploring is enabled we ignore touch events and transform - // the stream of hover events as touch events. This allows one consistent - // event stream to drive the keyboard since during touch exploring the - // first touch generates only hover events and tapping on the same - // location generates hover and touch events. - if (mAccessibilityManager.isTouchExplorationEnabled()) { - return true; - } - return onTouchEventInternal(event); - } - - private boolean onTouchEventInternal(MotionEvent me) { + public boolean onTouchEvent(MotionEvent me) { // Convert multi-pointer up/down events to single up/down events to // deal with the typical multi-pointer behavior of two-thumb typing final int pointerCount = me.getPointerCount(); diff --git a/core/java/android/inputmethodservice/SoftInputWindow.java b/core/java/android/inputmethodservice/SoftInputWindow.java index 7159260..df1afee 100644 --- a/core/java/android/inputmethodservice/SoftInputWindow.java +++ b/core/java/android/inputmethodservice/SoftInputWindow.java @@ -18,17 +18,13 @@ package android.inputmethodservice; import android.app.Dialog; import android.content.Context; -import android.content.pm.ActivityInfo; import android.graphics.Rect; import android.os.IBinder; import android.view.Gravity; import android.view.KeyEvent; import android.view.MotionEvent; -import android.view.View; import android.view.WindowManager; -import java.lang.Math; - /** * A SoftInputWindow is a Dialog that is intended to be used for a top-level input * method window. It will be displayed along the edge of the screen, moving @@ -46,7 +42,7 @@ class SoftInputWindow extends Dialog { } /** - * Create a DockWindow that uses a custom style. + * Create a SoftInputWindow that uses a custom style. * * @param context The Context in which the DockWindow should run. In * particular, it uses the window manager and theme from this context diff --git a/core/java/android/net/NetworkStats.java b/core/java/android/net/NetworkStats.java index 69ac1e7..3605652 100644 --- a/core/java/android/net/NetworkStats.java +++ b/core/java/android/net/NetworkStats.java @@ -16,10 +16,11 @@ package android.net; +import static com.android.internal.util.Preconditions.checkNotNull; + import android.os.Parcel; import android.os.Parcelable; import android.os.SystemClock; -import android.util.Log; import android.util.SparseBooleanArray; import com.android.internal.util.Objects; @@ -54,6 +55,8 @@ public class NetworkStats implements Parcelable { /** {@link #tag} value for total data across all tags. */ public static final int TAG_NONE = 0; + // TODO: move fields to "mVariable" notation + /** * {@link SystemClock#elapsedRealtime()} timestamp when this data was * generated. @@ -162,6 +165,17 @@ public class NetworkStats implements Parcelable { dest.writeLongArray(operations); } + @Override + public NetworkStats clone() { + final NetworkStats clone = new NetworkStats(elapsedRealtime, size); + NetworkStats.Entry entry = null; + for (int i = 0; i < size; i++) { + entry = getValues(i, entry); + clone.addValues(entry); + } + return clone; + } + // @VisibleForTesting public NetworkStats addIfaceValues( String iface, long rxBytes, long rxPackets, long txBytes, long txPackets) { @@ -295,8 +309,33 @@ public class NetworkStats implements Parcelable { */ public int findIndex(String iface, int uid, int set, int tag) { for (int i = 0; i < size; i++) { - if (Objects.equal(iface, this.iface[i]) && uid == this.uid[i] && set == this.set[i] - && tag == this.tag[i]) { + if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i] + && Objects.equal(iface, this.iface[i])) { + return i; + } + } + return -1; + } + + /** + * Find first stats index that matches the requested parameters, starting + * search around the hinted index as an optimization. + */ + // @VisibleForTesting + public int findIndexHinted(String iface, int uid, int set, int tag, int hintIndex) { + for (int offset = 0; offset < size; offset++) { + final int halfOffset = offset / 2; + + // search outwards from hint index, alternating forward and backward + final int i; + if (offset % 2 == 0) { + i = (hintIndex + halfOffset) % size; + } else { + i = (size + hintIndex - halfOffset - 1) % size; + } + + if (uid == this.uid[i] && set == this.set[i] && tag == this.tag[i] + && Objects.equal(iface, this.iface[i])) { return i; } } @@ -423,41 +462,11 @@ public class NetworkStats implements Parcelable { * Subtract the given {@link NetworkStats}, effectively leaving the delta * between two snapshots in time. Assumes that statistics rows collect over * time, and that none of them have disappeared. - * - * @throws IllegalArgumentException when given {@link NetworkStats} is - * non-monotonic. - */ - public NetworkStats subtract(NetworkStats value) { - return subtract(value, true, false); - } - - /** - * Subtract the given {@link NetworkStats}, effectively leaving the delta - * between two snapshots in time. Assumes that statistics rows collect over - * time, and that none of them have disappeared. - * <p> - * Instead of throwing when counters are non-monotonic, this variant clamps - * results to never be negative. */ - public NetworkStats subtractClamped(NetworkStats value) { - return subtract(value, false, true); - } - - /** - * Subtract the given {@link NetworkStats}, effectively leaving the delta - * between two snapshots in time. Assumes that statistics rows collect over - * time, and that none of them have disappeared. - * - * @param enforceMonotonic Validate that incoming value is strictly - * monotonic compared to this object. - * @param clampNegative Instead of throwing like {@code enforceMonotonic}, - * clamp resulting counters at 0 to prevent negative values. - */ - private NetworkStats subtract( - NetworkStats value, boolean enforceMonotonic, boolean clampNegative) { + public NetworkStats subtract(NetworkStats value) throws NonMonotonicException { final long deltaRealtime = this.elapsedRealtime - value.elapsedRealtime; - if (enforceMonotonic && deltaRealtime < 0) { - throw new IllegalArgumentException("found non-monotonic realtime"); + if (deltaRealtime < 0) { + throw new NonMonotonicException(this, value); } // result will have our rows, and elapsed time between snapshots @@ -470,7 +479,7 @@ public class NetworkStats implements Parcelable { entry.tag = tag[i]; // find remote row that matches, and subtract - final int j = value.findIndex(entry.iface, entry.uid, entry.set, entry.tag); + final int j = value.findIndexHinted(entry.iface, entry.uid, entry.set, entry.tag, i); if (j == -1) { // newly appearing row, return entire value entry.rxBytes = rxBytes[i]; @@ -485,20 +494,10 @@ public class NetworkStats implements Parcelable { entry.txBytes = txBytes[i] - value.txBytes[j]; entry.txPackets = txPackets[i] - value.txPackets[j]; entry.operations = operations[i] - value.operations[j]; - if (enforceMonotonic - && (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0 - || entry.txPackets < 0 || entry.operations < 0)) { - Log.v(TAG, "lhs=" + this); - Log.v(TAG, "rhs=" + value); - throw new IllegalArgumentException( - "found non-monotonic values at lhs[" + i + "] - rhs[" + j + "]"); - } - if (clampNegative) { - entry.rxBytes = Math.max(0, entry.rxBytes); - entry.rxPackets = Math.max(0, entry.rxPackets); - entry.txBytes = Math.max(0, entry.txBytes); - entry.txPackets = Math.max(0, entry.txPackets); - entry.operations = Math.max(0, entry.operations); + + if (entry.rxBytes < 0 || entry.rxPackets < 0 || entry.txBytes < 0 + || entry.txPackets < 0 || entry.operations < 0) { + throw new NonMonotonicException(this, i, value, j); } } @@ -564,12 +563,31 @@ public class NetworkStats implements Parcelable { return stats; } + /** + * Return all rows except those attributed to the requested UID; doesn't + * mutate the original structure. + */ + public NetworkStats withoutUid(int uid) { + final NetworkStats stats = new NetworkStats(elapsedRealtime, 10); + + Entry entry = new Entry(); + for (int i = 0; i < size; i++) { + entry = getValues(i, entry); + if (entry.uid != uid) { + stats.addValues(entry); + } + } + + return stats; + } + public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("NetworkStats: elapsedRealtime="); pw.println(elapsedRealtime); for (int i = 0; i < size; i++) { pw.print(prefix); - pw.print(" iface="); pw.print(iface[i]); + pw.print(" ["); pw.print(i); pw.print("]"); + pw.print(" iface="); pw.print(iface[i]); pw.print(" uid="); pw.print(uid[i]); pw.print(" set="); pw.print(setToString(set[i])); pw.print(" tag="); pw.print(tagToString(tag[i])); @@ -625,4 +643,23 @@ public class NetworkStats implements Parcelable { return new NetworkStats[size]; } }; + + public static class NonMonotonicException extends Exception { + public final NetworkStats left; + public final NetworkStats right; + public final int leftIndex; + public final int rightIndex; + + public NonMonotonicException(NetworkStats left, NetworkStats right) { + this(left, -1, right, -1); + } + + public NonMonotonicException( + NetworkStats left, int leftIndex, NetworkStats right, int rightIndex) { + this.left = checkNotNull(left, "missing left"); + this.right = checkNotNull(right, "missing right"); + this.leftIndex = leftIndex; + this.rightIndex = rightIndex; + } + } } diff --git a/core/java/android/net/TrafficStats.java b/core/java/android/net/TrafficStats.java index 18eb9f6..cd585b2 100644 --- a/core/java/android/net/TrafficStats.java +++ b/core/java/android/net/TrafficStats.java @@ -20,6 +20,7 @@ import android.app.DownloadManager; import android.app.backup.BackupManager; import android.content.Context; import android.media.MediaPlayer; +import android.net.NetworkStats.NonMonotonicException; import android.os.RemoteException; import android.os.ServiceManager; @@ -192,12 +193,15 @@ public class TrafficStats { throw new IllegalStateException("not profiling data"); } - // subtract starting values and return delta - final NetworkStats profilingStop = getDataLayerSnapshotForUid(context); - final NetworkStats profilingDelta = profilingStop.subtractClamped( - sActiveProfilingStart); - sActiveProfilingStart = null; - return profilingDelta; + try { + // subtract starting values and return delta + final NetworkStats profilingStop = getDataLayerSnapshotForUid(context); + final NetworkStats profilingDelta = profilingStop.subtract(sActiveProfilingStart); + sActiveProfilingStart = null; + return profilingDelta; + } catch (NonMonotonicException e) { + throw new RuntimeException(e); + } } } diff --git a/core/java/android/net/wimax/WimaxManagerConstants.java b/core/java/android/net/wimax/WimaxManagerConstants.java new file mode 100644 index 0000000..b4aaf5b --- /dev/null +++ b/core/java/android/net/wimax/WimaxManagerConstants.java @@ -0,0 +1,104 @@ +package android.net.wimax; + +/** + * {@hide} + */ +public class WimaxManagerConstants +{ + + /** + * Used by android.net.wimax.WimaxManager for handling management of + * Wimax access. + */ + public static final String WIMAX_SERVICE = "WiMax"; + + /** + * Broadcast intent action indicating that Wimax has been enabled, disabled, + * enabling, disabling, or unknown. One extra provides this state as an int. + * Another extra provides the previous state, if available. + */ + public static final String NET_4G_STATE_CHANGED_ACTION = + "android.net.fourG.NET_4G_STATE_CHANGED"; + + /** + * The lookup key for an int that indicates whether Wimax is enabled, + * disabled, enabling, disabling, or unknown. + */ + public static final String EXTRA_WIMAX_STATUS = "wimax_status"; + + /** + * Broadcast intent action indicating that Wimax state has been changed + * state could be scanning, connecting, connected, disconnecting, disconnected + * initializing, initialized, unknown and ready. One extra provides this state as an int. + * Another extra provides the previous state, if available. + */ + public static final String WIMAX_NETWORK_STATE_CHANGED_ACTION = + "android.net.fourG.wimax.WIMAX_NETWORK_STATE_CHANGED"; + + /** + * Broadcast intent action indicating that Wimax signal level has been changed. + * Level varies from 0 to 3. + */ + public static final String SIGNAL_LEVEL_CHANGED_ACTION = + "android.net.wimax.SIGNAL_LEVEL_CHANGED"; + + /** + * The lookup key for an int that indicates whether Wimax state is + * scanning, connecting, connected, disconnecting, disconnected + * initializing, initialized, unknown and ready. + */ + public static final String EXTRA_WIMAX_STATE = "WimaxState"; + public static final String EXTRA_4G_STATE = "4g_state"; + public static final String EXTRA_WIMAX_STATE_INT = "WimaxStateInt"; + /** + * The lookup key for an int that indicates whether state of Wimax + * is idle. + */ + public static final String EXTRA_WIMAX_STATE_DETAIL = "WimaxStateDetail"; + + /** + * The lookup key for an int that indicates Wimax signal level. + */ + public static final String EXTRA_NEW_SIGNAL_LEVEL = "newSignalLevel"; + + /** + * Indicatates Wimax is disabled. + */ + public static final int NET_4G_STATE_DISABLED = 1; + + /** + * Indicatates Wimax is enabled. + */ + public static final int NET_4G_STATE_ENABLED = 3; + + /** + * Indicatates Wimax status is known. + */ + public static final int NET_4G_STATE_UNKNOWN = 4; + + /** + * Indicatates Wimax is in idle state. + */ + public static final int WIMAX_IDLE = 6; + + /** + * Indicatates Wimax is being deregistered. + */ + public static final int WIMAX_DEREGISTRATION = 8; + + /** + * Indicatates wimax state is unknown. + */ + public static final int WIMAX_STATE_UNKNOWN = 0; + + /** + * Indicatates wimax state is connected. + */ + public static final int WIMAX_STATE_CONNECTED = 7; + + /** + * Indicatates wimax state is disconnected. + */ + public static final int WIMAX_STATE_DISCONNECTED = 9; + +} diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index fe0106d..33310df 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -768,61 +768,6 @@ public final class NfcAdapter { } /** - * TODO: Remove this once pre-built apk's (Maps, Youtube etc) are updated - * @deprecated use {@link CreateNdefMessageCallback} or {@link OnNdefPushCompleteCallback} - * @hide - */ - @Deprecated - public interface NdefPushCallback { - /** - * @deprecated use {@link CreateNdefMessageCallback} instead - */ - @Deprecated - NdefMessage createMessage(); - /** - * @deprecated use{@link OnNdefPushCompleteCallback} instead - */ - @Deprecated - void onMessagePushed(); - } - - /** - * TODO: Remove this - * Converts new callbacks to old callbacks. - */ - static final class LegacyCallbackWrapper implements CreateNdefMessageCallback, - OnNdefPushCompleteCallback { - final NdefPushCallback mLegacyCallback; - LegacyCallbackWrapper(NdefPushCallback legacyCallback) { - mLegacyCallback = legacyCallback; - } - @Override - public void onNdefPushComplete(NfcEvent event) { - mLegacyCallback.onMessagePushed(); - } - @Override - public NdefMessage createNdefMessage(NfcEvent event) { - return mLegacyCallback.createMessage(); - } - } - - /** - * TODO: Remove this once pre-built apk's (Maps, Youtube etc) are updated - * @deprecated use {@link #setNdefPushMessageCallback} instead - * @hide - */ - @Deprecated - public void enableForegroundNdefPush(Activity activity, final NdefPushCallback callback) { - if (activity == null || callback == null) { - throw new NullPointerException(); - } - enforceResumed(activity); - LegacyCallbackWrapper callbackWrapper = new LegacyCallbackWrapper(callback); - mNfcActivityManager.setNdefPushMessageCallback(activity, callbackWrapper); - mNfcActivityManager.setOnNdefPushCompleteCallback(activity, callbackWrapper); - } - - /** * Enable NDEF Push feature. * <p>This API is for the Settings application. * @hide diff --git a/core/java/android/os/Build.java b/core/java/android/os/Build.java index 17a882d..7d03494 100644 --- a/core/java/android/os/Build.java +++ b/core/java/android/os/Build.java @@ -277,7 +277,7 @@ public class Build { public static final int HONEYCOMB_MR2 = 13; /** - * Android 4.0. + * October 2011: Android 4.0. * * <p>Applications targeting this or a later release will get these * new changes in behavior:</p> @@ -309,6 +309,11 @@ public class Build { * </ul> */ public static final int ICE_CREAM_SANDWICH = 14; + + /** + * Android 4.1. + */ + public static final int ICE_CREAM_SANDWICH_MR1 = 15; } /** The type of build, like "user" or "eng". */ diff --git a/core/java/android/os/CountDownTimer.java b/core/java/android/os/CountDownTimer.java index 0c5c615..15e6405 100644 --- a/core/java/android/os/CountDownTimer.java +++ b/core/java/android/os/CountDownTimer.java @@ -25,7 +25,7 @@ import android.util.Log; * Example of showing a 30 second countdown in a text field: * * <pre class="prettyprint"> - * new CountdownTimer(30000, 1000) { + * new CountDownTimer(30000, 1000) { * * public void onTick(long millisUntilFinished) { * mTextField.setText("seconds remaining: " + millisUntilFinished / 1000); diff --git a/core/java/android/os/StrictMode.java b/core/java/android/os/StrictMode.java index 4d7a9bb..99f58a0 100644 --- a/core/java/android/os/StrictMode.java +++ b/core/java/android/os/StrictMode.java @@ -35,7 +35,6 @@ import dalvik.system.VMDebug; import java.io.PrintWriter; import java.io.StringWriter; import java.util.ArrayList; -import java.util.Collections; import java.util.HashMap; import java.util.Map; import java.util.concurrent.atomic.AtomicInteger; @@ -117,6 +116,14 @@ public final class StrictMode { private static final boolean IS_ENG_BUILD = "eng".equals(Build.TYPE); /** + * Boolean system property to disable strict mode checks outright. + * Set this to 'true' to force disable; 'false' has no effect on other + * enable/disable policy. + * @hide + */ + public static final String DISABLE_PROPERTY = "persist.sys.strictmode.disable"; + + /** * The boolean system property to control screen flashes on violations. * * @hide @@ -892,25 +899,31 @@ public final class StrictMode { * @hide */ public static boolean conditionallyEnableDebugLogging() { - boolean doFlashes = !amTheSystemServerProcess() && - SystemProperties.getBoolean(VISUAL_PROPERTY, IS_ENG_BUILD); + boolean doFlashes = SystemProperties.getBoolean(VISUAL_PROPERTY, false) + && !amTheSystemServerProcess(); + final boolean suppress = SystemProperties.getBoolean(DISABLE_PROPERTY, false); // For debug builds, log event loop stalls to dropbox for analysis. // Similar logic also appears in ActivityThread.java for system apps. - if (IS_USER_BUILD && !doFlashes) { + if (!doFlashes && (IS_USER_BUILD || suppress)) { setCloseGuardEnabled(false); return false; } + // Eng builds have flashes on all the time. The suppression property + // overrides this, so we force the behavior only after the short-circuit + // check above. + if (IS_ENG_BUILD) { + doFlashes = true; + } + + // Thread policy controls BlockGuard. int threadPolicyMask = StrictMode.DETECT_DISK_WRITE | StrictMode.DETECT_DISK_READ | StrictMode.DETECT_NETWORK; if (!IS_USER_BUILD) { threadPolicyMask |= StrictMode.PENALTY_DROPBOX; - if (IS_ENG_BUILD) { - threadPolicyMask |= StrictMode.PENALTY_LOG; - } } if (doFlashes) { threadPolicyMask |= StrictMode.PENALTY_FLASH; @@ -918,6 +931,8 @@ public final class StrictMode { StrictMode.setThreadPolicyMask(threadPolicyMask); + // VM Policy controls CloseGuard, detection of Activity leaks, + // and instance counting. if (IS_USER_BUILD) { setCloseGuardEnabled(false); } else { diff --git a/core/java/android/provider/CalendarContract.java b/core/java/android/provider/CalendarContract.java index 4b4d308..413150b 100644 --- a/core/java/android/provider/CalendarContract.java +++ b/core/java/android/provider/CalendarContract.java @@ -72,7 +72,7 @@ import android.util.Log; * {@link Calendars#MAX_REMINDERS} which is set by the Sync Adapter that owns * the given calendar. Reminders are specified in minutes before the event and * have a type.</li> - * <li>The {@link ExtendedProperties} table hold opaque data fields used by the + * <li>The {@link ExtendedProperties} table holds opaque data fields used by the * sync adapter. The provider takes no action with items in this table except to * delete them when their related events are deleted.</li> * </ul> @@ -300,8 +300,23 @@ public final class CalendarContract { public static final String CALENDAR_COLOR = "calendar_color"; /** + * A key for looking up a color from the {@link Colors} table. NULL or + * an empty string are reserved for indicating that the calendar does + * not use a key for looking up the color. The provider will update + * {@link #CALENDAR_COLOR} automatically when a valid key is written to + * this column. The key must reference an existing row of the + * {@link Colors} table. @see Colors + * <P> + * Type: TEXT + * </P> + */ + public static final String CALENDAR_COLOR_KEY = "calendar_color_index"; + + /** * The display name of the calendar. Column name. - * <P>Type: TEXT</P> + * <P> + * Type: TEXT + * </P> */ public static final String CALENDAR_DISPLAY_NAME = "calendar_displayName"; @@ -392,6 +407,28 @@ public final class CalendarContract { * <P>Type: TEXT</P> */ public static final String ALLOWED_REMINDERS = "allowedReminders"; + + /** + * A comma separated list of availability types supported for this + * calendar in the format "#,#,#". Valid types are + * {@link Events#AVAILABILITY_BUSY}, {@link Events#AVAILABILITY_FREE}, + * {@link Events#AVAILABILITY_TENTATIVE}. Setting this field to only + * {@link Events#AVAILABILITY_BUSY} should be used to indicate that + * changing the availability is not supported. + * + */ + public static final String ALLOWED_AVAILABILITY = "allowedAvailability"; + + /** + * A comma separated list of attendee types supported for this calendar + * in the format "#,#,#". Valid types are {@link Attendees#TYPE_NONE}, + * {@link Attendees#TYPE_OPTIONAL}, {@link Attendees#TYPE_REQUIRED}, + * {@link Attendees#TYPE_RESOURCE}. Setting this field to only + * {@link Attendees#TYPE_NONE} should be used to indicate that changing + * the attendee type is not supported. + * + */ + public static final String ALLOWED_ATTENDEE_TYPES = "allowedAttendeeTypes"; } /** @@ -527,6 +564,8 @@ public final class CalendarContract { * <li>{@link #SYNC_EVENTS} set to 1</li> * <li>{@link #CALENDAR_TIME_ZONE}</li> * <li>{@link #ALLOWED_REMINDERS}</li> + * <li>{@link #ALLOWED_AVAILABILITY}</li> + * <li>{@link #ALLOWED_ATTENDEE_TYPES}</li> * </ul> * <dt><b>Update</b></dt> * <dd>To perform an update on a calendar the {@link #_ID} of the calendar @@ -566,6 +605,8 @@ public final class CalendarContract { * <li>{@link #OWNER_ACCOUNT}</li> * <li>{@link #MAX_REMINDERS}</li> * <li>{@link #ALLOWED_REMINDERS}</li> + * <li>{@link #ALLOWED_AVAILABILITY}</li> + * <li>{@link #ALLOWED_ATTENDEE_TYPES}</li> * <li>{@link #CAN_MODIFY_TIME_ZONE}</li> * <li>{@link #CAN_ORGANIZER_RESPOND}</li> * <li>{@link #CAN_PARTIALLY_UPDATE}</li> @@ -688,13 +729,21 @@ public final class CalendarContract { /** * The type of attendee. Column name. - * <P>Type: Integer (one of {@link #TYPE_REQUIRED}, {@link #TYPE_OPTIONAL})</P> + * <P> + * Type: Integer (one of {@link #TYPE_NONE}, {@link #TYPE_REQUIRED}, + * {@link #TYPE_OPTIONAL}, {@link #TYPE_RESOURCE}) + * </P> */ public static final String ATTENDEE_TYPE = "attendeeType"; public static final int TYPE_NONE = 0; public static final int TYPE_REQUIRED = 1; public static final int TYPE_OPTIONAL = 2; + /** + * This specifies that an attendee is a resource, like a room, a + * cabbage, or something and not an actual person. + */ + public static final int TYPE_RESOURCE = 3; /** * The attendance status of the attendee. Column name. @@ -787,13 +836,26 @@ public final class CalendarContract { public static final String EVENT_LOCATION = "eventLocation"; /** - * A secondary color for the individual event. Reserved for future use. - * Column name. + * A secondary color for the individual event. This should only be + * updated by the sync adapter for a given account. * <P>Type: INTEGER</P> */ public static final String EVENT_COLOR = "eventColor"; /** + * A secondary color key for the individual event. NULL or an empty + * string are reserved for indicating that the event does not use a key + * for looking up the color. The provider will update + * {@link #EVENT_COLOR} automatically when a valid key is written to + * this column. The key must reference an existing row of the + * {@link Colors} table. @see Colors + * <P> + * Type: TEXT + * </P> + */ + public static final String EVENT_COLOR_KEY = "eventColor_index"; + + /** * The event status. Column name. * <P>Type: INTEGER (one of {@link #STATUS_TENTATIVE}...)</P> */ @@ -949,8 +1011,10 @@ public final class CalendarContract { /** * If this event counts as busy time or is still free time that can be * scheduled over. Column name. - * <P>Type: INTEGER (One of {@link #AVAILABILITY_BUSY}, - * {@link #AVAILABILITY_FREE})</P> + * <P> + * Type: INTEGER (One of {@link #AVAILABILITY_BUSY}, + * {@link #AVAILABILITY_FREE}, {@link #AVAILABILITY_TENTATIVE}) + * </P> */ public static final String AVAILABILITY = "availability"; @@ -964,6 +1028,11 @@ public final class CalendarContract { * other events. */ public static final int AVAILABILITY_FREE = 1; + /** + * Indicates that the owner's availability may change, but should be + * considered busy time that will conflict. + */ + public static final int AVAILABILITY_TENTATIVE = 2; /** * Whether the event has an alarm or not. Column name. @@ -1335,7 +1404,10 @@ public final class CalendarContract { * <dd>When inserting a new event the following fields must be included: * <ul> * <li>dtstart</li> - * <li>dtend -or- a (rrule or rdate) and a duration</li> + * <li>dtend if the event is non-recurring</li> + * <li>duration if the event is recurring</li> + * <li>rrule or rdate if the event is recurring</li> + * <li>eventTimezone</li> * <li>a calendar_id</li> * </ul> * There are also further requirements when inserting or updating an event. @@ -1473,6 +1545,8 @@ public final class CalendarContract { CAL_SYNC9, CAL_SYNC10, ALLOWED_REMINDERS, + ALLOWED_ATTENDEE_TYPES, + ALLOWED_AVAILABILITY, CALENDAR_ACCESS_LEVEL, CALENDAR_COLOR, CALENDAR_TIME_ZONE, @@ -2224,6 +2298,76 @@ public final class CalendarContract { } } + protected interface ColorsColumns extends SyncStateContract.Columns { + + /** + * The type of color, which describes how it should be used. Valid types + * are {@link #TYPE_CALENDAR} and {@link #TYPE_EVENT}. Column name. + * <P> + * Type: INTEGER (NOT NULL) + * </P> + */ + public static final String COLOR_TYPE = "color_type"; + + /** + * This indicateds a color that can be used for calendars. + */ + public static final int TYPE_CALENDAR = 0; + /** + * This indicates a color that can be used for events. + */ + public static final int TYPE_EVENT = 1; + + /** + * The key used to reference this color. This can be any non-empty + * string, but must be unique for a given {@link #ACCOUNT_TYPE} and + * {@link #ACCOUNT_NAME}. Column name. + * <P> + * Type: TEXT + * </P> + */ + public static final String COLOR_KEY = "color_index"; + + /** + * The color as an 8-bit ARGB integer value. Colors should specify alpha + * as fully opaque (eg 0xFF993322) as the alpha may be ignored or + * modified for display. It is reccomended that colors be usable with + * light (near white) text. Apps should not depend on that assumption, + * however. Column name. + * <P> + * Type: INTEGER (NOT NULL) + * </P> + */ + public static final String COLOR = "color"; + + } + + /** + * Fields for accessing colors available for a given account. Colors are + * referenced by {@link #COLOR_KEY} which must be unique for a given + * account name/type. These values can only be updated by the sync + * adapter. Only {@link #COLOR} may be updated after the initial insert. In + * addition, a row can only be deleted once all references to that color + * have been removed from the {@link Calendars} or {@link Events} tables. + */ + public static final class Colors implements ColorsColumns { + /** + * @hide + */ + public static final String TABLE_NAME = "Colors"; + /** + * The Uri for querying color information + */ + @SuppressWarnings("hiding") + public static final Uri CONTENT_URI = Uri.parse("content://" + AUTHORITY + "/colors"); + + /** + * This utility class cannot be instantiated + */ + private Colors() { + } + } + protected interface ExtendedPropertiesColumns { /** * The event the extended property belongs to. Column name. @@ -2247,7 +2391,7 @@ public final class CalendarContract { /** * Fields for accessing the Extended Properties. This is a generic set of - * name/value pairs for use by sync adapters or apps to add extra + * name/value pairs for use by sync adapters to add extra * information to events. There are three writable columns and all three * must be present when inserting a new value. They are: * <ul> diff --git a/core/java/android/provider/ContactsContract.java b/core/java/android/provider/ContactsContract.java index 4bc0892..821b6df 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -1673,7 +1673,6 @@ public final class ContactsContract { * Querying for social stream data requires android.permission.READ_SOCIAL_STREAM * permission. * </p> - * @hide */ public static final class StreamItems implements StreamItemsColumns { /** @@ -2736,7 +2735,6 @@ public final class ContactsContract { * inserting or updating social stream items requires android.permission.WRITE_SOCIAL_STREAM * permission. * </p> - * @hide */ public static final class StreamItems implements BaseColumns, StreamItemsColumns { /** @@ -3149,7 +3147,6 @@ public final class ContactsContract { * </pre> * </dd> * </dl> - * @hide */ public static final class StreamItems implements BaseColumns, StreamItemsColumns { /** @@ -3247,7 +3244,6 @@ public final class ContactsContract { * Columns in the StreamItems table. * * @see ContactsContract.StreamItems - * @hide */ protected interface StreamItemsColumns { /** @@ -3538,7 +3534,6 @@ public final class ContactsContract { * <pre> * </dd> * </dl> - * @hide */ public static final class StreamItemPhotos implements BaseColumns, StreamItemPhotosColumns { /** @@ -3566,7 +3561,6 @@ public final class ContactsContract { * Columns in the StreamItemPhotos table. * * @see ContactsContract.StreamItemPhotos - * @hide */ protected interface StreamItemPhotosColumns { /** @@ -4666,6 +4660,13 @@ public final class ContactsContract { * @hide */ public static final String CONTENT_TYPE = "vnd.android.cursor.dir/phone_lookup"; + + /** + * Boolean parameter that is used to look up a SIP address. + * + * @hide + */ + public static final String QUERY_PARAMETER_SIP_ADDRESS = "sip"; } /** diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index a0652f7..769776e 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1183,6 +1183,10 @@ public final class Settings { public static final String RADIO_WIFI = "wifi"; /** + * {@hide} + */ + public static final String RADIO_WIMAX = "wimax"; + /** * Constant for use in AIRPLANE_MODE_RADIOS to specify Cellular radio. */ public static final String RADIO_CELL = "cell"; @@ -2899,6 +2903,11 @@ public final class Settings { */ public static final String WIFI_NETWORKS_AVAILABLE_NOTIFICATION_ON = "wifi_networks_available_notification_on"; + /** + * {@hide} + */ + public static final String WIMAX_NETWORKS_AVAILABLE_NOTIFICATION_ON = + "wimax_networks_available_notification_on"; /** * Delay (in seconds) before repeating the Wi-Fi networks available notification. @@ -4068,6 +4077,13 @@ public final class Settings { "contacts_preauth_uri_expiration"; /** + * Whether the Messaging app posts notifications. + * 0=disabled. 1=enabled. + */ + public static final String MESSAGING_APP_NOTIFICATIONS = "messaging_app_notifications"; + + + /** * This are the settings to be backed up. * * NOTE: Settings are backed up and restored in the order they appear @@ -4104,7 +4120,8 @@ public final class Settings { MOUNT_UMS_NOTIFY_ENABLED, UI_NIGHT_MODE, LOCK_SCREEN_OWNER_INFO, - LOCK_SCREEN_OWNER_INFO_ENABLED + LOCK_SCREEN_OWNER_INFO_ENABLED, + MESSAGING_APP_NOTIFICATIONS }; /** diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 79995d0..8eb9da1 100755 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -1271,9 +1271,6 @@ public final class Telephony { Cursor cursor = SqliteWrapper.query(context, context.getContentResolver(), uri, ID_PROJECTION, null, null, null); - if (DEBUG) { - Log.v(TAG, "getOrCreateThreadId cursor cnt: " + cursor.getCount()); - } if (cursor != null) { try { if (cursor.moveToFirst()) { @@ -1841,5 +1838,15 @@ public final class Telephony { public static final String EXTRA_PLMN = "plmn"; public static final String EXTRA_SHOW_SPN = "showSpn"; public static final String EXTRA_SPN = "spn"; + + /** + * Activity Action: Shows a dialog to turn off Messaging app notification. + * <p>Input: Nothing. + * <p>Output: Nothing. + */ + @SdkConstant(SdkConstantType.ACTIVITY_INTENT_ACTION) + public static final String ACTION_MESSAGING_APP_NOTIFICATIONS = + "android.provider.Telephony.MESSAGING_APP_NOTIFICATIONS"; + } } diff --git a/core/java/android/service/textservice/SpellCheckerService.java b/core/java/android/service/textservice/SpellCheckerService.java index 2ecf307..28251a6 100644 --- a/core/java/android/service/textservice/SpellCheckerService.java +++ b/core/java/android/service/textservice/SpellCheckerService.java @@ -146,6 +146,14 @@ public abstract class SpellCheckerService extends Service { public void onCancel() {} /** + * Request to close this session. + * This function will run on the incoming IPC thread. + * So, this is not called on the main thread, + * but will be called in series on another thread. + */ + public void onClose() {} + + /** * @return Locale for this session */ public String getLocale() { @@ -162,7 +170,7 @@ public abstract class SpellCheckerService extends Service { // Preventing from exposing ISpellCheckerSession.aidl, create an internal class. private static class InternalISpellCheckerSession extends ISpellCheckerSession.Stub { - private final ISpellCheckerSessionListener mListener; + private ISpellCheckerSessionListener mListener; private final Session mSession; private final String mLocale; private final Bundle mBundle; @@ -192,6 +200,12 @@ public abstract class SpellCheckerService extends Service { mSession.onCancel(); } + @Override + public void onClose() { + mSession.onClose(); + mListener = null; + } + public String getLocale() { return mLocale; } diff --git a/core/java/android/service/wallpaper/WallpaperService.java b/core/java/android/service/wallpaper/WallpaperService.java index ba94ab2..a9a628a 100644 --- a/core/java/android/service/wallpaper/WallpaperService.java +++ b/core/java/android/service/wallpaper/WallpaperService.java @@ -148,7 +148,10 @@ public abstract class WallpaperService extends Service { int mCurWidth; int mCurHeight; int mWindowFlags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; + int mWindowPrivateFlags = + WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS; int mCurWindowFlags = mWindowFlags; + int mCurWindowPrivateFlags = mWindowPrivateFlags; final Rect mVisibleInsets = new Rect(); final Rect mWinFrame = new Rect(); final Rect mContentInsets = new Rect(); @@ -359,6 +362,25 @@ public abstract class WallpaperService extends Service { updateSurface(false, false, false); } } + + /** + * Control whether this wallpaper will receive notifications when the wallpaper + * has been scrolled. By default, wallpapers will receive notifications, although + * the default static image wallpapers do not. It is a performance optimization to + * set this to false. + * + * @param enabled whether the wallpaper wants to receive offset notifications + */ + public void setOffsetNotificationsEnabled(boolean enabled) { + mWindowPrivateFlags = enabled + ? (mWindowPrivateFlags | + WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS) + : (mWindowPrivateFlags & + ~WindowManager.LayoutParams.PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS); + if (mCreated) { + updateSurface(false, false, false); + } + } /** * Called once to initialize the engine. After returning, the @@ -478,6 +500,8 @@ public abstract class WallpaperService extends Service { out.print(prefix); out.print("mType="); out.print(mType); out.print(" mWindowFlags="); out.print(mWindowFlags); out.print(" mCurWindowFlags="); out.println(mCurWindowFlags); + out.print(" mWindowPrivateFlags="); out.print(mWindowPrivateFlags); + out.print(" mCurWindowPrivateFlags="); out.println(mCurWindowPrivateFlags); out.print(prefix); out.print("mVisibleInsets="); out.print(mVisibleInsets.toShortString()); out.print(" mWinFrame="); out.print(mWinFrame.toShortString()); @@ -528,7 +552,8 @@ public abstract class WallpaperService extends Service { final boolean formatChanged = mFormat != mSurfaceHolder.getRequestedFormat(); boolean sizeChanged = mWidth != myWidth || mHeight != myHeight; final boolean typeChanged = mType != mSurfaceHolder.getRequestedType(); - final boolean flagsChanged = mCurWindowFlags != mWindowFlags; + final boolean flagsChanged = mCurWindowFlags != mWindowFlags || + mCurWindowPrivateFlags != mWindowPrivateFlags; if (forceRelayout || creating || surfaceCreating || formatChanged || sizeChanged || typeChanged || flagsChanged || redrawNeeded) { @@ -554,6 +579,8 @@ public abstract class WallpaperService extends Service { | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE ; + mCurWindowPrivateFlags = mWindowPrivateFlags; + mLayout.privateFlags = mWindowPrivateFlags; mLayout.memoryType = mType; mLayout.token = mWindowToken; diff --git a/core/java/android/speech/tts/FileSynthesisCallback.java b/core/java/android/speech/tts/FileSynthesisCallback.java index 5808919..04c3377 100644 --- a/core/java/android/speech/tts/FileSynthesisCallback.java +++ b/core/java/android/speech/tts/FileSynthesisCallback.java @@ -16,10 +16,10 @@ package android.speech.tts; import android.media.AudioFormat; +import android.os.FileUtils; import android.util.Log; import java.io.File; -import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; import java.nio.ByteBuffer; @@ -63,7 +63,7 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { * Must be called while holding the monitor on {@link #mStateLock}. */ private void cleanUp() { - closeFile(); + closeFileAndWidenPermissions(); if (mFile != null) { mFileName.delete(); } @@ -72,7 +72,7 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { /** * Must be called while holding the monitor on {@link #mStateLock}. */ - private void closeFile() { + private void closeFileAndWidenPermissions() { try { if (mFile != null) { mFile.close(); @@ -81,6 +81,18 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { } catch (IOException ex) { Log.e(TAG, "Failed to close " + mFileName + ": " + ex); } + + try { + // Make the written file readable and writeable by everyone. + // This allows the app that requested synthesis to read the file. + // + // Note that the directory this file was written must have already + // been world writeable in order it to have been + // written to in the first place. + FileUtils.setPermissions(mFileName.getAbsolutePath(), 0666, -1, -1); //-rw-rw-rw + } catch (SecurityException se) { + Log.e(TAG, "Security exception setting rw permissions on : " + mFileName); + } } @Override @@ -168,7 +180,7 @@ class FileSynthesisCallback extends AbstractSynthesisCallback { int dataLength = (int) (mFile.length() - WAV_HEADER_LENGTH); mFile.write( makeWavHeader(mSampleRateInHz, mAudioFormat, mChannelCount, dataLength)); - closeFile(); + closeFileAndWidenPermissions(); mDone = true; return TextToSpeech.SUCCESS; } catch (IOException ex) { diff --git a/core/java/android/speech/tts/ITextToSpeechService.aidl b/core/java/android/speech/tts/ITextToSpeechService.aidl index ff3fa11..1a8c1fb 100644 --- a/core/java/android/speech/tts/ITextToSpeechService.aidl +++ b/core/java/android/speech/tts/ITextToSpeechService.aidl @@ -114,6 +114,21 @@ interface ITextToSpeechService { int isLanguageAvailable(in String lang, in String country, in String variant); /** + * Returns a list of features available for a given language. Elements of the returned + * string array can be passed in as keys to {@link TextToSpeech#speak} and + * {@link TextToSpeech#synthesizeToFile} to select a given feature or features to be + * used during synthesis. + * + * @param lang ISO-3 language code. + * @param country ISO-3 country code. May be empty or null. + * @param variant Language variant. May be empty or null. + * @return An array of strings containing the set of features supported for + * the supplied locale. The array of strings must not contain + * duplicates. + */ + String[] getFeaturesForLanguage(in String lang, in String country, in String variant); + + /** * Notifies the engine that it should load a speech synthesis language. * * @param lang ISO-3 language code. diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 98ab310..fdc2570 100755 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -31,10 +31,13 @@ import android.provider.Settings; import android.text.TextUtils; import android.util.Log; +import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Set; /** * @@ -147,7 +150,25 @@ public class TextToSpeech { } /** - * Constants and parameter names for controlling text-to-speech. + * Constants and parameter names for controlling text-to-speech. These include: + * + * <ul> + * <li> + * Intents to ask engine to install data or check its data and + * extras for a TTS engine's check data activity. + * </li> + * <li> + * Keys for the parameters passed with speak commands, e.g. + * {@link Engine#KEY_PARAM_UTTERANCE_ID}, {@link Engine#KEY_PARAM_STREAM}. + * </li> + * <li> + * A list of feature strings that engines might support, e.g + * {@link Engine#KEY_FEATURE_NETWORK_SYNTHESIS}). These values may be passed in to + * {@link TextToSpeech#speak} and {@link TextToSpeech#synthesizeToFile} to modify + * engine behaviour. The engine can be queried for the set of features it supports + * through {@link TextToSpeech#getFeatures(java.util.Locale)}. + * </li> + * </ul> */ public class Engine { @@ -435,6 +456,25 @@ public class TextToSpeech { */ public static final String KEY_PARAM_PAN = "pan"; + /** + * Feature key for network synthesis. See {@link TextToSpeech#getFeatures(Locale)} + * for a description of how feature keys work. If set (and supported by the engine + * as per {@link TextToSpeech#getFeatures(Locale)}, the engine must + * use network based synthesis. + * + * @see TextToSpeech#speak(String, int, java.util.HashMap) + * @see TextToSpeech#synthesizeToFile(String, java.util.HashMap, String) + * @see TextToSpeech#getFeatures(java.util.Locale) + */ + public static final String KEY_FEATURE_NETWORK_SYNTHESIS = "networkTts"; + + /** + * Feature key for embedded synthesis. See {@link TextToSpeech#getFeatures(Locale)} + * for a description of how feature keys work. If set and supported by the engine + * as per {@link TextToSpeech#getFeatures(Locale)}, the engine must synthesize + * text on-device (without making network requests). + */ + public static final String KEY_FEATURE_EMBEDDED_SYNTHESIS = "embeddedTts"; } private final Context mContext; @@ -812,6 +852,36 @@ public class TextToSpeech { } /** + * Queries the engine for the set of features it supports for a given locale. + * Features can either be framework defined, e.g. + * {@link TextToSpeech.Engine#KEY_FEATURE_NETWORK_SYNTHESIS} or engine specific. + * Engine specific keys must be prefixed by the name of the engine they + * are intended for. These keys can be used as parameters to + * {@link TextToSpeech#speak(String, int, java.util.HashMap)} and + * {@link TextToSpeech#synthesizeToFile(String, java.util.HashMap, String)}. + * + * Features are boolean flags, and their values in the synthesis parameters + * must be behave as per {@link Boolean#parseBoolean(String)}. + * + * @param locale The locale to query features for. + */ + public Set<String> getFeatures(final Locale locale) { + return runAction(new Action<Set<String>>() { + @Override + public Set<String> run(ITextToSpeechService service) throws RemoteException { + String[] features = service.getFeaturesForLanguage( + locale.getISO3Language(), locale.getISO3Country(), locale.getVariant()); + if (features != null) { + final Set<String> featureSet = new HashSet<String>(); + Collections.addAll(featureSet, features); + return featureSet; + } + return null; + } + }, null, "getFeatures"); + } + + /** * Checks whether the TTS engine is busy speaking. Note that a speech item is * considered complete once it's audio data has been sent to the audio mixer, or * written to a file. There might be a finite lag between this point, and when @@ -1017,6 +1087,10 @@ public class TextToSpeech { copyFloatParam(bundle, params, Engine.KEY_PARAM_VOLUME); copyFloatParam(bundle, params, Engine.KEY_PARAM_PAN); + // Copy feature strings defined by the framework. + copyStringParam(bundle, params, Engine.KEY_FEATURE_NETWORK_SYNTHESIS); + copyStringParam(bundle, params, Engine.KEY_FEATURE_EMBEDDED_SYNTHESIS); + // Copy over all parameters that start with the name of the // engine that we are currently connected to. The engine is // free to interpret them as it chooses. diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index 48739ba..83b6d4c 100644 --- a/core/java/android/speech/tts/TextToSpeechService.java +++ b/core/java/android/speech/tts/TextToSpeechService.java @@ -36,6 +36,7 @@ import java.io.File; import java.io.IOException; import java.util.HashMap; import java.util.Locale; +import java.util.Set; /** @@ -67,7 +68,6 @@ import java.util.Locale; * any. Any pending data from the current synthesis will be discarded. * */ -// TODO: Add a link to the sample TTS engine once it's done. public abstract class TextToSpeechService extends Service { private static final boolean DBG = false; @@ -196,6 +196,18 @@ public abstract class TextToSpeechService extends Service { protected abstract void onSynthesizeText(SynthesisRequest request, SynthesisCallback callback); + /** + * Queries the service for a set of features supported for a given language. + * + * @param lang ISO-3 language code. + * @param country ISO-3 country code. May be empty or null. + * @param variant Language variant. May be empty or null. + * @return A list of features supported for the given language. + */ + protected Set<String> onGetFeaturesForLanguage(String lang, String country, String variant) { + return null; + } + private int getDefaultSpeechRate() { return getSecureSettingInt(Settings.Secure.TTS_DEFAULT_RATE, Engine.DEFAULT_RATE); } @@ -778,6 +790,18 @@ public abstract class TextToSpeechService extends Service { return onIsLanguageAvailable(lang, country, variant); } + public String[] getFeaturesForLanguage(String lang, String country, String variant) { + Set<String> features = onGetFeaturesForLanguage(lang, country, variant); + String[] featuresArray = null; + if (features != null) { + featuresArray = new String[features.size()]; + features.toArray(featuresArray); + } else { + featuresArray = new String[0]; + } + return featuresArray; + } + /* * There is no point loading a non default language if defaults * are enforced. diff --git a/core/java/android/text/DynamicLayout.java b/core/java/android/text/DynamicLayout.java index f82c9c4..026af34 100644 --- a/core/java/android/text/DynamicLayout.java +++ b/core/java/android/text/DynamicLayout.java @@ -281,9 +281,9 @@ extends Layout } reflowed.generate(text, where, where + after, - getPaint(), getWidth(), getAlignment(), getTextDirectionHeuristic(), - getSpacingMultiplier(), getSpacingAdd(), - false, true, mEllipsizedWidth, mEllipsizeAt); + getPaint(), getWidth(), getTextDirectionHeuristic(), getSpacingMultiplier(), + getSpacingAdd(), false, + true, mEllipsizedWidth, mEllipsizeAt); int n = reflowed.getLineCount(); // If the new layout has a blank line at the end, but it is not diff --git a/core/java/android/text/StaticLayout.java b/core/java/android/text/StaticLayout.java index 583cbe6..1dd4c8a 100644 --- a/core/java/android/text/StaticLayout.java +++ b/core/java/android/text/StaticLayout.java @@ -137,9 +137,9 @@ public class StaticLayout extends Layout { mMeasured = MeasuredText.obtain(); - generate(source, bufstart, bufend, paint, outerwidth, align, textDir, - spacingmult, spacingadd, includepad, includepad, - ellipsizedWidth, ellipsize); + generate(source, bufstart, bufend, paint, outerwidth, textDir, spacingmult, + spacingadd, includepad, includepad, ellipsizedWidth, + ellipsize); mMeasured = MeasuredText.recycle(mMeasured); mFontMetricsInt = null; @@ -157,10 +157,10 @@ public class StaticLayout extends Layout { /* package */ void generate(CharSequence source, int bufStart, int bufEnd, TextPaint paint, int outerWidth, - Alignment align, TextDirectionHeuristic textDir, - float spacingmult, float spacingadd, - boolean includepad, boolean trackpad, - float ellipsizedWidth, TextUtils.TruncateAt ellipsize) { + TextDirectionHeuristic textDir, float spacingmult, + float spacingadd, boolean includepad, + boolean trackpad, float ellipsizedWidth, + TextUtils.TruncateAt ellipsize) { mLineCount = 0; int v = 0; @@ -328,9 +328,7 @@ public class StaticLayout extends Layout { whichPaint = mWorkPaint; } - float wid = bm.getWidth() * - -whichPaint.ascent() / - bm.getHeight(); + float wid = bm.getWidth() * -whichPaint.ascent() / bm.getHeight(); w += wid; hasTabOrEmoji = true; @@ -398,67 +396,49 @@ public class StaticLayout extends Layout { okBottom = fitBottom; } } else { - final boolean moreChars = (j + 1 < spanEnd); - if (ok != here) { - // Log.e("text", "output ok " + here + " to " +ok); + final boolean moreChars = (j + 1 < spanEnd); + int endPos; + int above, below, top, bottom; + float currentTextWidth; - while (ok < spanEnd && chs[ok - paraStart] == CHAR_SPACE) { - ok++; - } + if (ok != here) { + // If it is a space that makes the length exceed width, cut here + if (c == CHAR_SPACE) ok = j + 1; - v = out(source, - here, ok, - okAscent, okDescent, okTop, okBottom, - v, - spacingmult, spacingadd, chooseHt, - chooseHtv, fm, hasTabOrEmoji, - needMultiply, paraStart, chdirs, dir, easy, - ok == bufEnd, includepad, trackpad, - chs, widths, paraStart, - ellipsize, ellipsizedWidth, okWidth, - paint, moreChars); - - here = ok; - } else if (fit != here) { - // Log.e("text", "output fit " + here + " to " +fit); - v = out(source, - here, fit, - fitAscent, fitDescent, - fitTop, fitBottom, - v, - spacingmult, spacingadd, chooseHt, - chooseHtv, fm, hasTabOrEmoji, - needMultiply, paraStart, chdirs, dir, easy, - fit == bufEnd, includepad, trackpad, - chs, widths, paraStart, - ellipsize, ellipsizedWidth, fitWidth, - paint, moreChars); - - here = fit; - } else { - // Log.e("text", "output one " + here + " to " +(here + 1)); - // XXX not sure why the existing fm wasn't ok. - // measureText(paint, mWorkPaint, - // source, here, here + 1, fm, tab, - // null); - - v = out(source, - here, here+1, - fm.ascent, fm.descent, - fm.top, fm.bottom, - v, - spacingmult, spacingadd, chooseHt, - chooseHtv, fm, hasTabOrEmoji, - needMultiply, paraStart, chdirs, dir, easy, - here + 1 == bufEnd, includepad, - trackpad, - chs, widths, paraStart, - ellipsize, ellipsizedWidth, - widths[here - paraStart], paint, moreChars); - - here = here + 1; + while (ok < spanEnd && chs[ok - paraStart] == CHAR_SPACE) { + ok++; } + endPos = ok; + above = okAscent; + below = okDescent; + top = okTop; + bottom = okBottom; + currentTextWidth = okWidth; + } else if (fit != here) { + endPos = fit; + above = fitAscent; + below = fitDescent; + top = fitTop; + bottom = fitBottom; + currentTextWidth = fitWidth; + } else { + endPos = here + 1; + above = fm.ascent; + below = fm.descent; + top = fm.top; + bottom = fm.bottom; + currentTextWidth = widths[here - paraStart]; + } + + v = out(source, here, endPos, + above, below, top, bottom, + v, spacingmult, spacingadd, chooseHt,chooseHtv, fm, hasTabOrEmoji, + needMultiply, chdirs, dir, easy, bufEnd, includepad, trackpad, + chs, widths, paraStart, ellipsize, ellipsizedWidth, + currentTextWidth, paint, moreChars); + here = endPos; + if (here < spanStart) { // didn't output all the text for this span // we've measured the raw widths, though, so @@ -501,10 +481,10 @@ public class StaticLayout extends Layout { v, spacingmult, spacingadd, chooseHt, chooseHtv, fm, hasTabOrEmoji, - needMultiply, paraStart, chdirs, dir, easy, - paraEnd == bufEnd, includepad, trackpad, - chs, widths, paraStart, - ellipsize, ellipsizedWidth, w, paint, paraEnd != bufEnd); + needMultiply, chdirs, dir, easy, bufEnd, + includepad, trackpad, chs, + widths, paraStart, ellipsize, + ellipsizedWidth, w, paint, paraEnd != bufEnd); } paraStart = paraEnd; @@ -525,10 +505,10 @@ public class StaticLayout extends Layout { v, spacingmult, spacingadd, null, null, fm, false, - needMultiply, bufEnd, null, DEFAULT_DIR, true, - true, includepad, trackpad, - null, null, bufStart, - ellipsize, ellipsizedWidth, 0, paint, false); + needMultiply, null, DEFAULT_DIR, true, bufEnd, + includepad, trackpad, null, + null, bufStart, ellipsize, + ellipsizedWidth, 0, paint, false); } } @@ -628,12 +608,12 @@ public class StaticLayout extends Layout { float spacingmult, float spacingadd, LineHeightSpan[] chooseHt, int[] chooseHtv, Paint.FontMetricsInt fm, boolean hasTabOrEmoji, - boolean needMultiply, int pstart, byte[] chdirs, - int dir, boolean easy, boolean last, - boolean includePad, boolean trackPad, - char[] chs, float[] widths, int widthStart, - TextUtils.TruncateAt ellipsize, float ellipsisWidth, - float textWidth, TextPaint paint, boolean moreChars) { + boolean needMultiply, byte[] chdirs, int dir, + boolean easy, int bufEnd, boolean includePad, + boolean trackPad, char[] chs, + float[] widths, int widthStart, TextUtils.TruncateAt ellipsize, + float ellipsisWidth, float textWidth, + TextPaint paint, boolean moreChars) { int j = mLineCount; int off = j * mColumns; int want = off + mColumns + TOP; @@ -683,7 +663,7 @@ public class StaticLayout extends Layout { above = top; } } - if (last) { + if (end == bufEnd) { if (trackPad) { mBottomPadding = bottom - below; } diff --git a/core/java/android/text/method/ArrowKeyMovementMethod.java b/core/java/android/text/method/ArrowKeyMovementMethod.java index e93039b..4ec4bc4 100644 --- a/core/java/android/text/method/ArrowKeyMovementMethod.java +++ b/core/java/android/text/method/ArrowKeyMovementMethod.java @@ -197,16 +197,18 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme @Override protected boolean leftWord(TextView widget, Spannable buffer) { final int selectionEnd = widget.getSelectionEnd(); - mWordIterator.setCharSequence(buffer, selectionEnd, selectionEnd); - return Selection.moveToPreceding(buffer, mWordIterator, isSelecting(buffer)); + final WordIterator wordIterator = widget.getWordIterator(); + wordIterator.setCharSequence(buffer, selectionEnd, selectionEnd); + return Selection.moveToPreceding(buffer, wordIterator, isSelecting(buffer)); } /** {@hide} */ @Override protected boolean rightWord(TextView widget, Spannable buffer) { final int selectionEnd = widget.getSelectionEnd(); - mWordIterator.setCharSequence(buffer, selectionEnd, selectionEnd); - return Selection.moveToFollowing(buffer, mWordIterator, isSelecting(buffer)); + final WordIterator wordIterator = widget.getWordIterator(); + wordIterator.setCharSequence(buffer, selectionEnd, selectionEnd); + return Selection.moveToFollowing(buffer, wordIterator, isSelecting(buffer)); } @Override @@ -322,8 +324,6 @@ public class ArrowKeyMovementMethod extends BaseMovementMethod implements Moveme return sInstance; } - private WordIterator mWordIterator = new WordIterator(); - private static final Object LAST_TAP_DOWN = new Object(); private static ArrowKeyMovementMethod sInstance; } diff --git a/core/java/android/text/style/SuggestionSpan.java b/core/java/android/text/style/SuggestionSpan.java index 6e95016..ed2af10 100644 --- a/core/java/android/text/style/SuggestionSpan.java +++ b/core/java/android/text/style/SuggestionSpan.java @@ -60,7 +60,6 @@ public class SuggestionSpan extends CharacterStyle implements ParcelableSpan { * Sets this flag if the auto correction is about to be applied to a word/text * that the user is typing/composing. This type of suggestion is rendered differently * to indicate the auto correction is happening. - * @hide */ public static final int FLAG_AUTO_CORRECTION = 0x0004; diff --git a/core/java/android/view/IApplicationToken.aidl b/core/java/android/view/IApplicationToken.aidl index 6bff5b3..5f0600f 100644 --- a/core/java/android/view/IApplicationToken.aidl +++ b/core/java/android/view/IApplicationToken.aidl @@ -20,6 +20,7 @@ package android.view; /** {@hide} */ interface IApplicationToken { + void windowsDrawn(); void windowsVisible(); void windowsGone(); boolean keyDispatchingTimedOut(); diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 61b13d5..62e6ebd 100644 --- a/core/java/android/view/View.java +++ b/core/java/android/view/View.java @@ -1548,7 +1548,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal int mID = NO_ID; /** - * The stable ID of this view for accessibility porposes. + * The stable ID of this view for accessibility purposes. */ int mAccessibilityViewId = NO_ID; @@ -2317,55 +2317,59 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal private int mBackgroundResource; private boolean mBackgroundSizeChanged; - /** - * Listener used to dispatch focus change events. - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected OnFocusChangeListener mOnFocusChangeListener; + static class ListenerInfo { + /** + * Listener used to dispatch focus change events. + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected OnFocusChangeListener mOnFocusChangeListener; - /** - * Listeners for layout change events. - */ - private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners; + /** + * Listeners for layout change events. + */ + private ArrayList<OnLayoutChangeListener> mOnLayoutChangeListeners; - /** - * Listeners for attach events. - */ - private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners; + /** + * Listeners for attach events. + */ + private CopyOnWriteArrayList<OnAttachStateChangeListener> mOnAttachStateChangeListeners; - /** - * Listener used to dispatch click events. - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected OnClickListener mOnClickListener; + /** + * Listener used to dispatch click events. + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + public OnClickListener mOnClickListener; - /** - * Listener used to dispatch long click events. - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected OnLongClickListener mOnLongClickListener; + /** + * Listener used to dispatch long click events. + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected OnLongClickListener mOnLongClickListener; - /** - * Listener used to build the context menu. - * This field should be made private, so it is hidden from the SDK. - * {@hide} - */ - protected OnCreateContextMenuListener mOnCreateContextMenuListener; + /** + * Listener used to build the context menu. + * This field should be made private, so it is hidden from the SDK. + * {@hide} + */ + protected OnCreateContextMenuListener mOnCreateContextMenuListener; + + private OnKeyListener mOnKeyListener; - private OnKeyListener mOnKeyListener; + private OnTouchListener mOnTouchListener; - private OnTouchListener mOnTouchListener; + private OnHoverListener mOnHoverListener; - private OnHoverListener mOnHoverListener; + private OnGenericMotionListener mOnGenericMotionListener; - private OnGenericMotionListener mOnGenericMotionListener; + private OnDragListener mOnDragListener; - private OnDragListener mOnDragListener; + private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener; + } - private OnSystemUiVisibilityChangeListener mOnSystemUiVisibilityChangeListener; + ListenerInfo mListenerInfo; /** * The application environment this view lives in. @@ -3346,13 +3350,21 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal return mVerticalScrollbarPosition; } + ListenerInfo getListenerInfo() { + if (mListenerInfo != null) { + return mListenerInfo; + } + mListenerInfo = new ListenerInfo(); + return mListenerInfo; + } + /** * Register a callback to be invoked when focus of this view changed. * * @param l The callback that will run. */ public void setOnFocusChangeListener(OnFocusChangeListener l) { - mOnFocusChangeListener = l; + getListenerInfo().mOnFocusChangeListener = l; } /** @@ -3362,11 +3374,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param listener The listener that will be called when layout bounds change. */ public void addOnLayoutChangeListener(OnLayoutChangeListener listener) { - if (mOnLayoutChangeListeners == null) { - mOnLayoutChangeListeners = new ArrayList<OnLayoutChangeListener>(); + ListenerInfo li = getListenerInfo(); + if (li.mOnLayoutChangeListeners == null) { + li.mOnLayoutChangeListeners = new ArrayList<OnLayoutChangeListener>(); } - if (!mOnLayoutChangeListeners.contains(listener)) { - mOnLayoutChangeListeners.add(listener); + if (!li.mOnLayoutChangeListeners.contains(listener)) { + li.mOnLayoutChangeListeners.add(listener); } } @@ -3376,10 +3389,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param listener The listener for layout bounds change. */ public void removeOnLayoutChangeListener(OnLayoutChangeListener listener) { - if (mOnLayoutChangeListeners == null) { + ListenerInfo li = mListenerInfo; + if (li == null || li.mOnLayoutChangeListeners == null) { return; } - mOnLayoutChangeListeners.remove(listener); + li.mOnLayoutChangeListeners.remove(listener); } /** @@ -3393,10 +3407,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #removeOnAttachStateChangeListener(OnAttachStateChangeListener) */ public void addOnAttachStateChangeListener(OnAttachStateChangeListener listener) { - if (mOnAttachStateChangeListeners == null) { - mOnAttachStateChangeListeners = new CopyOnWriteArrayList<OnAttachStateChangeListener>(); + ListenerInfo li = getListenerInfo(); + if (li.mOnAttachStateChangeListeners == null) { + li.mOnAttachStateChangeListeners + = new CopyOnWriteArrayList<OnAttachStateChangeListener>(); } - mOnAttachStateChangeListeners.add(listener); + li.mOnAttachStateChangeListeners.add(listener); } /** @@ -3407,10 +3423,11 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @see #addOnAttachStateChangeListener(OnAttachStateChangeListener) */ public void removeOnAttachStateChangeListener(OnAttachStateChangeListener listener) { - if (mOnAttachStateChangeListeners == null) { + ListenerInfo li = mListenerInfo; + if (li == null || li.mOnAttachStateChangeListeners == null) { return; } - mOnAttachStateChangeListeners.remove(listener); + li.mOnAttachStateChangeListeners.remove(listener); } /** @@ -3419,7 +3436,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @return The callback, or null if one is not registered. */ public OnFocusChangeListener getOnFocusChangeListener() { - return mOnFocusChangeListener; + ListenerInfo li = mListenerInfo; + return li != null ? li.mOnFocusChangeListener : null; } /** @@ -3434,7 +3452,16 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (!isClickable()) { setClickable(true); } - mOnClickListener = l; + getListenerInfo().mOnClickListener = l; + } + + /** + * Return whether this view has an attached OnClickListener. Returns + * true if there is a listener, false if there is none. + */ + public boolean hasOnClickListeners() { + ListenerInfo li = mListenerInfo; + return (li != null && li.mOnClickListener != null); } /** @@ -3449,7 +3476,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (!isLongClickable()) { setLongClickable(true); } - mOnLongClickListener = l; + getListenerInfo().mOnLongClickListener = l; } /** @@ -3463,11 +3490,13 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (!isLongClickable()) { setLongClickable(true); } - mOnCreateContextMenuListener = l; + getListenerInfo().mOnCreateContextMenuListener = l; } /** - * Call this view's OnClickListener, if it is defined. + * Call this view's OnClickListener, if it is defined. Performs all normal + * actions associated with clicking: reporting accessibility event, playing + * a sound, etc. * * @return True there was an assigned OnClickListener that was called, false * otherwise is returned. @@ -3475,9 +3504,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal public boolean performClick() { sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); - if (mOnClickListener != null) { + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); - mOnClickListener.onClick(this); + li.mOnClickListener.onClick(this); return true; } @@ -3485,6 +3515,23 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } /** + * Directly call any attached OnClickListener. Unlike {@link #performClick()}, + * this only calls the listener, and does not do any associated clicking + * actions like reporting an accessibility event. + * + * @return True there was an assigned OnClickListener that was called, false + * otherwise is returned. + */ + public boolean callOnClick() { + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnClickListener != null) { + li.mOnClickListener.onClick(this); + return true; + } + return false; + } + + /** * Call this view's OnLongClickListener, if it is defined. Invokes the context menu if the * OnLongClickListener did not consume the event. * @@ -3494,8 +3541,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_LONG_CLICKED); boolean handled = false; - if (mOnLongClickListener != null) { - handled = mOnLongClickListener.onLongClick(View.this); + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnLongClickListener != null) { + handled = li.mOnLongClickListener.onLongClick(View.this); } if (!handled) { handled = showContextMenu(); @@ -3563,7 +3611,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param l the key listener to attach to this view */ public void setOnKeyListener(OnKeyListener l) { - mOnKeyListener = l; + getListenerInfo().mOnKeyListener = l; } /** @@ -3571,7 +3619,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param l the touch listener to attach to this view */ public void setOnTouchListener(OnTouchListener l) { - mOnTouchListener = l; + getListenerInfo().mOnTouchListener = l; } /** @@ -3579,7 +3627,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param l the generic motion listener to attach to this view */ public void setOnGenericMotionListener(OnGenericMotionListener l) { - mOnGenericMotionListener = l; + getListenerInfo().mOnGenericMotionListener = l; } /** @@ -3587,7 +3635,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param l the hover listener to attach to this view */ public void setOnHoverListener(OnHoverListener l) { - mOnHoverListener = l; + getListenerInfo().mOnHoverListener = l; } /** @@ -3598,7 +3646,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param l An implementation of {@link android.view.View.OnDragListener}. */ public void setOnDragListener(OnDragListener l) { - mOnDragListener = l; + getListenerInfo().mOnDragListener = l; } /** @@ -3804,8 +3852,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal } invalidate(true); - if (mOnFocusChangeListener != null) { - mOnFocusChangeListener.onFocusChange(this, gainFocus); + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnFocusChangeListener != null) { + li.mOnFocusChangeListener.onFocusChange(this, gainFocus); } if (mAttachInfo != null) { @@ -4122,10 +4171,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal bounds.offset(locationOnScreen[0], locationOnScreen[1]); info.setBoundsInScreen(bounds); - ViewParent parent = getParent(); - if (parent instanceof View) { - View parentView = (View) parent; - info.setParent(parentView); + if ((mPrivateFlags & IS_ROOT_NAMESPACE) == 0) { + ViewParent parent = getParent(); + if (parent instanceof View) { + View parentView = (View) parent; + info.setParent(parentView); + } } info.setPackageName(mContext.getPackageName()); @@ -4219,6 +4270,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * * @attr ref android.R.styleable#View_contentDescription */ + @RemotableViewMethod public void setContentDescription(CharSequence contentDescription) { mContentDescription = contentDescription; } @@ -5438,8 +5490,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal // Give any attached key listener a first crack at the event. //noinspection SimplifiableIfStatement - if (mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED - && mOnKeyListener.onKey(this, event.getKeyCode(), event)) { + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED + && li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) { return true; } @@ -5478,8 +5531,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement - if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && - mOnTouchListener.onTouch(this, event)) { + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED + && li.mOnTouchListener.onTouch(this, event)) { return true; } @@ -5571,8 +5625,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal private boolean dispatchGenericMotionEventInternal(MotionEvent event) { //noinspection SimplifiableIfStatement - if (mOnGenericMotionListener != null && (mViewFlags & ENABLED_MASK) == ENABLED - && mOnGenericMotionListener.onGenericMotion(this, event)) { + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnGenericMotionListener != null + && (mViewFlags & ENABLED_MASK) == ENABLED + && li.mOnGenericMotionListener.onGenericMotion(this, event)) { return true; } @@ -5598,8 +5654,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ protected boolean dispatchHoverEvent(MotionEvent event) { //noinspection SimplifiableIfStatement - if (mOnHoverListener != null && (mViewFlags & ENABLED_MASK) == ENABLED - && mOnHoverListener.onHover(this, event)) { + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnHoverListener != null + && (mViewFlags & ENABLED_MASK) == ENABLED + && li.mOnHoverListener.onHover(this, event)) { return true; } @@ -5883,7 +5941,8 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mAttachInfo.mKeepScreenOn = true; } mAttachInfo.mSystemUiVisibility |= mSystemUiVisibility; - if (mOnSystemUiVisibilityChangeListener != null) { + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnSystemUiVisibilityChangeListener != null) { mAttachInfo.mHasSystemUiListeners = true; } } @@ -6117,8 +6176,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal ((MenuBuilder)menu).setCurrentMenuInfo(menuInfo); onCreateContextMenu(menu); - if (mOnCreateContextMenuListener != null) { - mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo); + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnCreateContextMenuListener != null) { + li.mOnCreateContextMenuListener.onCreateContextMenu(menu, this, menuInfo); } // Clear the extra information so subsequent items that aren't mine don't @@ -9722,8 +9782,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal performCollectViewAttributes(visibility); onAttachedToWindow(); + ListenerInfo li = mListenerInfo; final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners = - mOnAttachStateChangeListeners; + li != null ? li.mOnAttachStateChangeListeners : null; if (listeners != null && listeners.size() > 0) { // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to // perform the dispatching. The iterator is a safe guard against listeners that @@ -9755,8 +9816,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal onDetachedFromWindow(); + ListenerInfo li = mListenerInfo; final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners = - mOnAttachStateChangeListeners; + li != null ? li.mOnAttachStateChangeListeners : null; if (listeners != null && listeners.size() > 0) { // NOTE: because of the use of CopyOnWriteArrayList, we *must* use an iterator to // perform the dispatching. The iterator is a safe guard against listeners that @@ -10114,8 +10176,20 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal mLocalDirtyRect.setEmpty(); } + // The layer is not valid if the underlying GPU resources cannot be allocated + if (!mHardwareLayer.isValid()) { + return null; + } + HardwareCanvas currentCanvas = mAttachInfo.mHardwareCanvas; final HardwareCanvas canvas = mHardwareLayer.start(currentCanvas); + + // Make sure all the GPU resources have been properly allocated + if (canvas == null) { + mHardwareLayer.end(currentCanvas); + return null; + } + mAttachInfo.mHardwareCanvas = canvas; try { canvas.setViewport(width, height); @@ -11180,9 +11254,10 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal onLayout(changed, l, t, r, b); mPrivateFlags &= ~LAYOUT_REQUIRED; - if (mOnLayoutChangeListeners != null) { + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnLayoutChangeListeners != null) { ArrayList<OnLayoutChangeListener> listenersCopy = - (ArrayList<OnLayoutChangeListener>) mOnLayoutChangeListeners.clone(); + (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone(); int numListeners = listenersCopy.size(); for (int i = 0; i < numListeners; ++i) { listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB); @@ -13052,7 +13127,7 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param l The {@link OnSystemUiVisibilityChangeListener} to receive callbacks. */ public void setOnSystemUiVisibilityChangeListener(OnSystemUiVisibilityChangeListener l) { - mOnSystemUiVisibilityChangeListener = l; + getListenerInfo().mOnSystemUiVisibilityChangeListener = l; if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) { mParent.recomputeViewAttributes(this); } @@ -13063,8 +13138,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * the view hierarchy. */ public void dispatchSystemUiVisibilityChanged(int visibility) { - if (mOnSystemUiVisibilityChangeListener != null) { - mOnSystemUiVisibilityChangeListener.onSystemUiVisibilityChange( + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnSystemUiVisibilityChangeListener != null) { + li.mOnSystemUiVisibilityChangeListener.onSystemUiVisibilityChange( visibility & PUBLIC_STATUS_BAR_VISIBILITY_MASK); } } @@ -13336,8 +13412,9 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal */ public boolean dispatchDragEvent(DragEvent event) { //noinspection SimplifiableIfStatement - if (mOnDragListener != null && (mViewFlags & ENABLED_MASK) == ENABLED - && mOnDragListener.onDrag(this, event)) { + ListenerInfo li = mListenerInfo; + if (li != null && li.mOnDragListener != null && (mViewFlags & ENABLED_MASK) == ENABLED + && li.mOnDragListener.onDrag(this, event)) { return true; } return onDragEvent(event); diff --git a/core/java/android/view/ViewGroup.java b/core/java/android/view/ViewGroup.java index 62b20b3..e366e72 100644 --- a/core/java/android/view/ViewGroup.java +++ b/core/java/android/view/ViewGroup.java @@ -352,7 +352,7 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager private static final int ARRAY_CAPACITY_INCREMENT = 12; // Used to draw cached views - private final Paint mCachePaint = new Paint(); + private Paint mCachePaint; // Used to animate add/remove changes in layout private LayoutTransition mTransition; @@ -405,8 +405,6 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager mChildren = new View[ARRAY_INITIAL_CAPACITY]; mChildrenCount = 0; - mCachePaint.setDither(false); - mPersistentDrawingCache = PERSISTENT_SCROLLING_CACHE; } @@ -2231,14 +2229,10 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager @Override void onInitializeAccessibilityNodeInfoInternal(AccessibilityNodeInfo info) { super.onInitializeAccessibilityNodeInfoInternal(info); - // If the view is not the topmost one in the view hierarchy and it is - // marked as the logical root of a view hierarchy, do not go any deeper. - if ((!(getParent() instanceof ViewRootImpl)) && (mPrivateFlags & IS_ROOT_NAMESPACE) != 0) { - return; - } for (int i = 0, count = mChildrenCount; i < count; i++) { View child = mChildren[i]; - if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) { + if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE + && (child.mPrivateFlags & IS_ROOT_NAMESPACE) == 0) { info.addChild(child); } } @@ -2909,6 +2903,11 @@ public abstract class ViewGroup extends View implements ViewParent, ViewManager if (layerType == LAYER_TYPE_NONE) { cachePaint = mCachePaint; + if (cachePaint == null) { + cachePaint = new Paint(); + cachePaint.setDither(false); + mCachePaint = cachePaint; + } if (alpha < 1.0f) { cachePaint.setAlpha((int) (alpha * 255)); mGroupFlags |= FLAG_ALPHA_LOWER_THAN_ONE; diff --git a/core/java/android/view/ViewRootImpl.java b/core/java/android/view/ViewRootImpl.java index 081e267..f7078ec 100644 --- a/core/java/android/view/ViewRootImpl.java +++ b/core/java/android/view/ViewRootImpl.java @@ -218,6 +218,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, boolean mNewSurfaceNeeded; boolean mHasHadWindowFocus; boolean mLastWasImTarget; + InputEventMessage mPendingInputEvents = null; boolean mWindowAttributesChanged = false; int mWindowAttributesChangesFlag = 0; @@ -287,7 +288,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, final AccessibilityManager mAccessibilityManager; - AccessibilityInteractionController mAccessibilityInteractionContrtoller; + AccessibilityInteractionController mAccessibilityInteractionController; AccessibilityInteractionConnectionManager mAccessibilityInteractionConnectionManager; @@ -431,20 +432,17 @@ public final class ViewRootImpl extends Handler implements ViewParent, } } - // If the application owns the surface, don't enable hardware acceleration - if (mSurfaceHolder == null) { - enableHardwareAcceleration(attrs); - } - CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get(); mTranslator = compatibilityInfo.getTranslator(); - if (mTranslator != null) { - mSurface.setCompatibilityTranslator(mTranslator); + // If the application owns the surface, don't enable hardware acceleration + if (mSurfaceHolder == null) { + enableHardwareAcceleration(attrs); } boolean restore = false; if (mTranslator != null) { + mSurface.setCompatibilityTranslator(mTranslator); restore = true; attrs.backup(); mTranslator.translateWindowLayout(attrs); @@ -596,6 +594,9 @@ public final class ViewRootImpl extends Handler implements ViewParent, mAttachInfo.mHardwareAccelerated = false; mAttachInfo.mHardwareAccelerationRequested = false; + // Don't enable hardware acceleration when the application is in compatibility mode + if (mTranslator != null) return; + // Try to enable hardware acceleration if requested final boolean hardwareAccelerated = (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0; @@ -832,10 +833,24 @@ public final class ViewRootImpl extends Handler implements ViewParent, } } + private void processInputEvents(boolean outOfOrder) { + while (mPendingInputEvents != null) { + handleMessage(mPendingInputEvents.mMessage); + InputEventMessage tmpMessage = mPendingInputEvents; + mPendingInputEvents = mPendingInputEvents.mNext; + tmpMessage.recycle(); + if (outOfOrder) { + removeMessages(PROCESS_INPUT_EVENTS); + } + } + } + private void performTraversals() { // cache mView since it is used so much below... final View host = mView; + processInputEvents(true); + if (DBG) { System.out.println("======================================"); System.out.println("performTraversals"); @@ -2336,6 +2351,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_ACCESSIBILITY_ID = 1021; public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID = 1022; public final static int DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT = 1023; + public final static int PROCESS_INPUT_EVENTS = 1024; @Override public String getMessageName(Message message) { @@ -2388,7 +2404,9 @@ public final class ViewRootImpl extends Handler implements ViewParent, return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_ID"; case DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT: return "DO_FIND_ACCESSIBLITY_NODE_INFO_BY_VIEW_TEXT"; - + case PROCESS_INPUT_EVENTS: + return "PROCESS_INPUT_EVENTS"; + } return super.getMessageName(message); } @@ -2447,6 +2465,9 @@ public final class ViewRootImpl extends Handler implements ViewParent, case DISPATCH_GENERIC_MOTION: deliverGenericMotionEvent((MotionEvent) msg.obj, msg.arg1 != 0); break; + case PROCESS_INPUT_EVENTS: + processInputEvents(false); + break; case DISPATCH_APP_VISIBILITY: handleAppVisibility(msg.arg1 != 0); break; @@ -3526,10 +3547,10 @@ public final class ViewRootImpl extends Handler implements ViewParent, throw new IllegalStateException("getAccessibilityInteractionController" + " called when there is no mView"); } - if (mAccessibilityInteractionContrtoller == null) { - mAccessibilityInteractionContrtoller = new AccessibilityInteractionController(); + if (mAccessibilityInteractionController == null) { + mAccessibilityInteractionController = new AccessibilityInteractionController(); } - return mAccessibilityInteractionContrtoller; + return mAccessibilityInteractionController; } private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, @@ -3744,7 +3765,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, msg.obj = ri; sendMessage(msg); } - + private long mInputEventReceiveTimeNanos; private long mInputEventDeliverTimeNanos; private long mInputEventDeliverPostImeTimeNanos; @@ -3762,6 +3783,78 @@ public final class ViewRootImpl extends Handler implements ViewParent, } }; + /** + * Utility class used to queue up input events which are then handled during + * performTraversals(). Doing it this way allows us to ensure that we are up to date with + * all input events just prior to drawing, instead of placing those events on the regular + * handler queue, potentially behind a drawing event. + */ + static class InputEventMessage { + Message mMessage; + InputEventMessage mNext; + + private static final Object sPoolSync = new Object(); + private static InputEventMessage sPool; + private static int sPoolSize = 0; + + private static final int MAX_POOL_SIZE = 10; + + private InputEventMessage(Message m) { + mMessage = m; + mNext = null; + } + + /** + * Return a new Message instance from the global pool. Allows us to + * avoid allocating new objects in many cases. + */ + public static InputEventMessage obtain(Message msg) { + synchronized (sPoolSync) { + if (sPool != null) { + InputEventMessage m = sPool; + sPool = m.mNext; + m.mNext = null; + sPoolSize--; + m.mMessage = msg; + return m; + } + } + return new InputEventMessage(msg); + } + + /** + * Return the message to the pool. + */ + public void recycle() { + mMessage.recycle(); + synchronized (sPoolSync) { + if (sPoolSize < MAX_POOL_SIZE) { + mNext = sPool; + sPool = this; + sPoolSize++; + } + } + + } + } + + /** + * Place the input event message at the end of the current pending list + */ + private void enqueueInputEvent(Message msg, long when) { + InputEventMessage inputMessage = InputEventMessage.obtain(msg); + if (mPendingInputEvents == null) { + mPendingInputEvents = inputMessage; + } else { + InputEventMessage currMessage = mPendingInputEvents; + while (currMessage.mNext != null) { + currMessage = currMessage.mNext; + } + currMessage.mNext = inputMessage; + } + sendEmptyMessageAtTime(PROCESS_INPUT_EVENTS, when); + } + public void dispatchKey(KeyEvent event) { dispatchKey(event, false); } @@ -3786,7 +3879,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, if (LOCAL_LOGV) Log.v( TAG, "sending key " + event + " to " + mView); - sendMessageAtTime(msg, event.getEventTime()); + enqueueInputEvent(msg, event.getEventTime()); } private void dispatchMotion(MotionEvent event, boolean sendDone) { @@ -3804,21 +3897,21 @@ public final class ViewRootImpl extends Handler implements ViewParent, Message msg = obtainMessage(DISPATCH_POINTER); msg.obj = event; msg.arg1 = sendDone ? 1 : 0; - sendMessageAtTime(msg, event.getEventTime()); + enqueueInputEvent(msg, event.getEventTime()); } private void dispatchTrackball(MotionEvent event, boolean sendDone) { Message msg = obtainMessage(DISPATCH_TRACKBALL); msg.obj = event; msg.arg1 = sendDone ? 1 : 0; - sendMessageAtTime(msg, event.getEventTime()); + enqueueInputEvent(msg, event.getEventTime()); } private void dispatchGenericMotion(MotionEvent event, boolean sendDone) { Message msg = obtainMessage(DISPATCH_GENERIC_MOTION); msg.obj = event; msg.arg1 = sendDone ? 1 : 0; - sendMessageAtTime(msg, event.getEventTime()); + enqueueInputEvent(msg, event.getEventTime()); } public void dispatchAppVisibility(boolean visible) { @@ -4473,19 +4566,20 @@ public final class ViewRootImpl extends Handler implements ViewParent, * AccessibilityManagerService to the latter can interact with * the view hierarchy in this ViewAncestor. */ - final class AccessibilityInteractionConnection + static final class AccessibilityInteractionConnection extends IAccessibilityInteractionConnection.Stub { - private final WeakReference<ViewRootImpl> mViewAncestor; + private final WeakReference<ViewRootImpl> mRootImpl; AccessibilityInteractionConnection(ViewRootImpl viewAncestor) { - mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor); + mRootImpl = new WeakReference<ViewRootImpl>(viewAncestor); } public void findAccessibilityNodeInfoByAccessibilityId(int accessibilityId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, long interrogatingTid) { - if (mViewAncestor.get() != null) { - getAccessibilityInteractionController() + ViewRootImpl viewRootImpl = mRootImpl.get(); + if (viewRootImpl != null) { + viewRootImpl.getAccessibilityInteractionController() .findAccessibilityNodeInfoByAccessibilityIdClientThread(accessibilityId, interactionId, callback, interrogatingPid, interrogatingTid); } @@ -4494,8 +4588,9 @@ public final class ViewRootImpl extends Handler implements ViewParent, public void performAccessibilityAction(int accessibilityId, int action, int interactionId, IAccessibilityInteractionConnectionCallback callback, int interogatingPid, long interrogatingTid) { - if (mViewAncestor.get() != null) { - getAccessibilityInteractionController() + ViewRootImpl viewRootImpl = mRootImpl.get(); + if (viewRootImpl != null) { + viewRootImpl.getAccessibilityInteractionController() .performAccessibilityActionClientThread(accessibilityId, action, interactionId, callback, interogatingPid, interrogatingTid); } @@ -4504,8 +4599,9 @@ public final class ViewRootImpl extends Handler implements ViewParent, public void findAccessibilityNodeInfoByViewId(int viewId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, long interrogatingTid) { - if (mViewAncestor.get() != null) { - getAccessibilityInteractionController() + ViewRootImpl viewRootImpl = mRootImpl.get(); + if (viewRootImpl != null) { + viewRootImpl.getAccessibilityInteractionController() .findAccessibilityNodeInfoByViewIdClientThread(viewId, interactionId, callback, interrogatingPid, interrogatingTid); } @@ -4514,8 +4610,9 @@ public final class ViewRootImpl extends Handler implements ViewParent, public void findAccessibilityNodeInfosByViewText(String text, int accessibilityId, int interactionId, IAccessibilityInteractionConnectionCallback callback, int interrogatingPid, long interrogatingTid) { - if (mViewAncestor.get() != null) { - getAccessibilityInteractionController() + ViewRootImpl viewRootImpl = mRootImpl.get(); + if (viewRootImpl != null) { + viewRootImpl.getAccessibilityInteractionController() .findAccessibilityNodeInfosByViewTextClientThread(text, accessibilityId, interactionId, callback, interrogatingPid, interrogatingTid); } diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index e8ab227..e74fec6 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -813,6 +813,17 @@ public interface WindowManager extends ViewManager { public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002; /** + * By default, wallpapers are sent new offsets when the wallpaper is scrolled. Wallpapers + * may elect to skp these notifications if they are no doing anything productive with + * them (they do not affect the wallpaper scrolling operation) by calling + * {@link + * android.service.wallpaper.WallpaperService.Engine#setOffsetNotificationsEnabled(boolean)}. + * + * @hide + */ + public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004; + + /** * Control flags that are private to the platform. * @hide */ diff --git a/core/java/android/view/accessibility/AccessibilityNodeInfo.java b/core/java/android/view/accessibility/AccessibilityNodeInfo.java index 7671312..fa34ee7 100644 --- a/core/java/android/view/accessibility/AccessibilityNodeInfo.java +++ b/core/java/android/view/accessibility/AccessibilityNodeInfo.java @@ -293,7 +293,7 @@ public class AccessibilityNodeInfo implements Parcelable { */ public AccessibilityNodeInfo getParent() { enforceSealed(); - if (!canPerformRequestOverConnection(mAccessibilityViewId)) { + if (!canPerformRequestOverConnection(mParentAccessibilityViewId)) { return null; } AccessibilityInteractionClient client = AccessibilityInteractionClient.getInstance(); diff --git a/core/java/android/view/accessibility/AccessibilityRecord.java b/core/java/android/view/accessibility/AccessibilityRecord.java index fe06d98..a4e0688 100644 --- a/core/java/android/view/accessibility/AccessibilityRecord.java +++ b/core/java/android/view/accessibility/AccessibilityRecord.java @@ -391,8 +391,6 @@ public class AccessibilityRecord { * Gets the max scroll offset of the source left edge in pixels. * * @return The max scroll. - * - * @hide */ public int getMaxScrollX() { return mMaxScrollX; @@ -401,8 +399,6 @@ public class AccessibilityRecord { * Sets the max scroll offset of the source left edge in pixels. * * @param maxScrollX The max scroll. - * - * @hide */ public void setMaxScrollX(int maxScrollX) { enforceNotSealed(); @@ -413,8 +409,6 @@ public class AccessibilityRecord { * Gets the max scroll offset of the source top edge in pixels. * * @return The max scroll. - * - * @hide */ public int getMaxScrollY() { return mMaxScrollY; @@ -424,8 +418,6 @@ public class AccessibilityRecord { * Sets the max scroll offset of the source top edge in pixels. * * @param maxScrollY The max scroll. - * - * @hide */ public void setMaxScrollY(int maxScrollY) { enforceNotSealed(); diff --git a/core/java/android/view/inputmethod/InputMethodManager.java b/core/java/android/view/inputmethod/InputMethodManager.java index 3ead9df..b41e6f5 100644 --- a/core/java/android/view/inputmethod/InputMethodManager.java +++ b/core/java/android/view/inputmethod/InputMethodManager.java @@ -384,14 +384,18 @@ public final class InputMethodManager { } } - class ControlledInputConnectionWrapper extends IInputConnectionWrapper { - public ControlledInputConnectionWrapper(Looper mainLooper, InputConnection conn) { + private static class ControlledInputConnectionWrapper extends IInputConnectionWrapper { + private final InputMethodManager mParentInputMethodManager; + + public ControlledInputConnectionWrapper(final Looper mainLooper, final InputConnection conn, + final InputMethodManager inputMethodManager) { super(mainLooper, conn); + mParentInputMethodManager = inputMethodManager; } @Override public boolean isActive() { - return mActive; + return mParentInputMethodManager.mActive; } } @@ -439,7 +443,7 @@ public final class InputMethodManager { mMainLooper = looper; mH = new H(looper); mIInputContext = new ControlledInputConnectionWrapper(looper, - mDummyInputConnection); + mDummyInputConnection, this); if (mInstance == null) { mInstance = this; @@ -1016,7 +1020,7 @@ public final class InputMethodManager { mCursorCandStart = -1; mCursorCandEnd = -1; mCursorRect.setEmpty(); - servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic); + servedContext = new ControlledInputConnectionWrapper(vh.getLooper(), ic, this); } else { servedContext = null; } diff --git a/core/java/android/view/textservice/SpellCheckerSession.java b/core/java/android/view/textservice/SpellCheckerSession.java index 793f514..01b114c 100644 --- a/core/java/android/view/textservice/SpellCheckerSession.java +++ b/core/java/android/view/textservice/SpellCheckerSession.java @@ -123,7 +123,7 @@ public class SpellCheckerSession { } mSpellCheckerInfo = info; mSpellCheckerSessionListenerImpl = new SpellCheckerSessionListenerImpl(mHandler); - mInternalListener = new InternalListener(); + mInternalListener = new InternalListener(mSpellCheckerSessionListenerImpl); mTextServicesManager = tsm; mIsUsed = true; mSpellCheckerSessionListener = listener; @@ -152,6 +152,7 @@ public class SpellCheckerSession { public void close() { mIsUsed = false; try { + mSpellCheckerSessionListenerImpl.close(); mTextServicesManager.finishSpellCheckerService(mSpellCheckerSessionListenerImpl); } catch (RemoteException e) { // do nothing @@ -190,6 +191,7 @@ public class SpellCheckerSession { private static class SpellCheckerSessionListenerImpl extends ISpellCheckerSessionListener.Stub { private static final int TASK_CANCEL = 1; private static final int TASK_GET_SUGGESTIONS_MULTIPLE = 2; + private static final int TASK_CLOSE = 3; private final Queue<SpellCheckerParams> mPendingTasks = new LinkedList<SpellCheckerParams>(); private final Handler mHandler; @@ -224,6 +226,9 @@ public class SpellCheckerSession { case TASK_GET_SUGGESTIONS_MULTIPLE: processGetSuggestionsMultiple(scp); break; + case TASK_CLOSE: + processClose(); + break; } } @@ -247,6 +252,13 @@ public class SpellCheckerSession { suggestionsLimit, sequentialWords)); } + public void close() { + if (DBG) { + Log.w(TAG, "close"); + } + processOrEnqueueTask(new SpellCheckerParams(TASK_CLOSE, null, 0, false)); + } + public boolean isDisconnected() { return mOpened && mISpellCheckerSession == null; } @@ -284,6 +296,21 @@ public class SpellCheckerSession { } } + private void processClose() { + if (!checkOpenConnection()) { + return; + } + if (DBG) { + Log.w(TAG, "Close spell checker tasks."); + } + try { + mISpellCheckerSession.onClose(); + mISpellCheckerSession = null; + } catch (RemoteException e) { + Log.e(TAG, "Failed to close " + e); + } + } + private void processGetSuggestionsMultiple(SpellCheckerParams scp) { if (!checkOpenConnection()) { return; @@ -316,13 +343,19 @@ public class SpellCheckerSession { public void onGetSuggestions(SuggestionsInfo[] results); } - private class InternalListener extends ITextServicesSessionListener.Stub { + private static class InternalListener extends ITextServicesSessionListener.Stub { + private final SpellCheckerSessionListenerImpl mParentSpellCheckerSessionListenerImpl; + + public InternalListener(SpellCheckerSessionListenerImpl spellCheckerSessionListenerImpl) { + mParentSpellCheckerSessionListenerImpl = spellCheckerSessionListenerImpl; + } + @Override public void onServiceConnected(ISpellCheckerSession session) { if (DBG) { Log.w(TAG, "SpellCheckerSession connected."); } - mSpellCheckerSessionListenerImpl.onServiceConnected(session); + mParentSpellCheckerSessionListenerImpl.onServiceConnected(session); } } diff --git a/core/java/android/view/textservice/SuggestionsInfo.java b/core/java/android/view/textservice/SuggestionsInfo.java index 62a06b9..ddd0361 100644 --- a/core/java/android/view/textservice/SuggestionsInfo.java +++ b/core/java/android/view/textservice/SuggestionsInfo.java @@ -39,6 +39,12 @@ public final class SuggestionsInfo implements Parcelable { * word looks like a typo. */ public static final int RESULT_ATTR_LOOKS_LIKE_TYPO = 0x0002; + /** + * Flag of the attributes of the suggestions that can be obtained by + * {@link #getSuggestionsAttributes}: this tells that the text service thinks + * the result suggestions include highly recommended ones. + */ + public static final int RESULT_ATTR_HAS_RECOMMENDED_SUGGESTIONS = 0x0004; private final int mSuggestionsAttributes; private final String[] mSuggestions; private final boolean mSuggestionsAvailable; diff --git a/core/java/android/webkit/BrowserFrame.java b/core/java/android/webkit/BrowserFrame.java index 2cc928f..388920c 100644 --- a/core/java/android/webkit/BrowserFrame.java +++ b/core/java/android/webkit/BrowserFrame.java @@ -813,8 +813,6 @@ class BrowserFrame extends Handler { boolean synchronous, String username, String password) { - PerfChecker checker = new PerfChecker(); - if (mSettings.getCacheMode() != WebSettings.LOAD_DEFAULT) { cacheMode = mSettings.getCacheMode(); } @@ -872,11 +870,6 @@ class BrowserFrame extends Handler { || headers.containsKey("If-None-Match") ? WebSettings.LOAD_NO_CACHE : cacheMode); // Set referrer to current URL? - if (!loader.executeLoad()) { - checker.responseAlert("startLoadingResource fail"); - } - checker.responseAlert("startLoadingResource succeed"); - return !synchronous ? loadListener : null; } diff --git a/core/java/android/webkit/HTML5VideoFullScreen.java b/core/java/android/webkit/HTML5VideoFullScreen.java index 0ea27a0..f29aff2 100644 --- a/core/java/android/webkit/HTML5VideoFullScreen.java +++ b/core/java/android/webkit/HTML5VideoFullScreen.java @@ -150,7 +150,9 @@ public class HTML5VideoFullScreen extends HTML5VideoView private void prepareForFullScreen() { // So in full screen, we reset the MediaPlayer mPlayer.reset(); - setMediaController(new MediaController(mProxy.getContext())); + MediaController mc = new MediaController(mProxy.getContext()); + mc.setSystemUiVisibility(mLayout.getSystemUiVisibility()); + setMediaController(mc); mPlayer.setScreenOnWhilePlaying(true); prepareDataAndDisplayMode(mProxy); } diff --git a/core/java/android/webkit/HTML5VideoInline.java b/core/java/android/webkit/HTML5VideoInline.java index 42581c2..fe5908e 100644 --- a/core/java/android/webkit/HTML5VideoInline.java +++ b/core/java/android/webkit/HTML5VideoInline.java @@ -74,11 +74,13 @@ public class HTML5VideoInline extends HTML5VideoView{ public SurfaceTexture getSurfaceTexture(int videoLayerId) { // Create the surface texture. if (videoLayerId != mVideoLayerUsingSurfaceTexture - || mSurfaceTexture == null) { - if (mTextureNames == null) { - mTextureNames = new int[1]; - GLES20.glGenTextures(1, mTextureNames, 0); + || mSurfaceTexture == null + || mTextureNames == null) { + if (mTextureNames != null) { + GLES20.glDeleteTextures(1, mTextureNames, 0); } + mTextureNames = new int[1]; + GLES20.glGenTextures(1, mTextureNames, 0); mSurfaceTexture = new SurfaceTexture(mTextureNames[0]); } mVideoLayerUsingSurfaceTexture = videoLayerId; diff --git a/core/java/android/webkit/JWebCoreJavaBridge.java b/core/java/android/webkit/JWebCoreJavaBridge.java index 5b78586..b498435 100644 --- a/core/java/android/webkit/JWebCoreJavaBridge.java +++ b/core/java/android/webkit/JWebCoreJavaBridge.java @@ -87,11 +87,9 @@ final class JWebCoreJavaBridge extends Handler { * Call native timer callbacks. */ private void fireSharedTimer() { - PerfChecker checker = new PerfChecker(); // clear the flag so that sharedTimerFired() can set a new timer mHasInstantTimer = false; sharedTimerFired(); - checker.responseAlert("sharedTimer"); } /** diff --git a/core/java/android/webkit/JniUtil.java b/core/java/android/webkit/JniUtil.java index 7759ff3..4662040 100644 --- a/core/java/android/webkit/JniUtil.java +++ b/core/java/android/webkit/JniUtil.java @@ -22,6 +22,7 @@ import android.net.Uri; import android.provider.Settings; import android.util.Log; +import java.io.File; import java.io.InputStream; class JniUtil { @@ -79,7 +80,12 @@ class JniUtil { checkInitialized(); if (sCacheDirectory == null) { - sCacheDirectory = sContext.getCacheDir().getAbsolutePath(); + File cacheDir = sContext.getCacheDir(); + if (cacheDir == null) { + sCacheDirectory = ""; + } else { + sCacheDirectory = cacheDir.getAbsolutePath(); + } } return sCacheDirectory; diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java index 04af738..37e8bc0 100644 --- a/core/java/android/webkit/LoadListener.java +++ b/core/java/android/webkit/LoadListener.java @@ -1136,7 +1136,6 @@ class LoadListener extends Handler implements EventHandler { // Give the data to WebKit now. We don't have to synchronize on // mDataBuilder here because pulling each chunk removes it from the // internal list so it cannot be modified. - PerfChecker checker = new PerfChecker(); ByteArrayBuilder.Chunk c; while (true) { c = mDataBuilder.getFirstChunk(); @@ -1152,7 +1151,6 @@ class LoadListener extends Handler implements EventHandler { } else { c.release(); } - checker.responseAlert("res nativeAddData"); } } @@ -1173,13 +1171,11 @@ class LoadListener extends Handler implements EventHandler { WebViewWorker.MSG_REMOVE_CACHE, this).sendToTarget(); } if (mNativeLoader != 0) { - PerfChecker checker = new PerfChecker(); if (!mSetNativeResponse) { setNativeResponse(); } nativeFinished(); - checker.responseAlert("res nativeFinished"); clearNativeLoader(); } } diff --git a/core/java/android/webkit/PerfChecker.java b/core/java/android/webkit/PerfChecker.java deleted file mode 100644 index 8c5f86e..0000000 --- a/core/java/android/webkit/PerfChecker.java +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright (C) 2007 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.webkit; - -import android.os.SystemClock; -import android.util.Log; - -class PerfChecker { - - private long mTime; - private static final long mResponseThreshold = 2000; // 2s - - public PerfChecker() { - if (false) { - mTime = SystemClock.uptimeMillis(); - } - } - - /** - * @param what log string - * Logs given string if mResponseThreshold time passed between either - * instantiation or previous responseAlert call - */ - public void responseAlert(String what) { - if (false) { - long upTime = SystemClock.uptimeMillis(); - long time = upTime - mTime; - if (time > mResponseThreshold) { - Log.w("webkit", what + " used " + time + " ms"); - } - // Reset mTime, to permit reuse - mTime = upTime; - } - } -} diff --git a/core/java/android/webkit/WebChromeClient.java b/core/java/android/webkit/WebChromeClient.java index ae40ded..3d129f7 100644 --- a/core/java/android/webkit/WebChromeClient.java +++ b/core/java/android/webkit/WebChromeClient.java @@ -95,27 +95,33 @@ public class WebChromeClient { public void onHideCustomView() {} /** - * Request the host application to create a new Webview. The host - * application should handle placement of the new WebView in the view - * system. The default behavior returns null. - * @param view The WebView that initiated the callback. - * @param dialog True if the new window is meant to be a small dialog - * window. - * @param userGesture True if the request was initiated by a user gesture - * such as clicking a link. - * @param resultMsg The message to send when done creating a new WebView. - * Set the new WebView through resultMsg.obj which is - * WebView.WebViewTransport() and then call - * resultMsg.sendToTarget(); - * @return Similar to javscript dialogs, this method should return true if - * the client is going to handle creating a new WebView. Note that - * the WebView will halt processing if this method returns true so - * make sure to call resultMsg.sendToTarget(). It is undefined - * behavior to call resultMsg.sendToTarget() after returning false - * from this method. + * Request the host application to create a new window. If the host + * application chooses to honor this request, it should return true from + * this method, create a new WebView to host the window, insert it into the + * View system and send the supplied resultMsg message to its target with + * the new WebView as an argument. If the host application chooses not to + * honor the request, it should return false from this method. The default + * implementation of this method does nothing and hence returns false. + * @param view The WebView from which the request for a new window + * originated. + * @param isDialog True if the new window should be a dialog, rather than + * a full-size window. + * @param isUserGesture True if the request was initiated by a user gesture, + * such as the user clicking a link. + * @param resultMsg The message to send when once a new WebView has been + * created. resultMsg.obj is a + * {@link WebView.WebViewTransport} object. This should be + * used to transport the new WebView, by calling + * {@link WebView.WebViewTransport#setWebView(WebView) + * WebView.WebViewTransport.setWebView(WebView)}. + * @return This method should return true if the host application will + * create a new window, in which case resultMsg should be sent to + * its target. Otherwise, this method should return false. Returning + * false from this method but also sending resultMsg will result in + * undefined behavior. */ - public boolean onCreateWindow(WebView view, boolean dialog, - boolean userGesture, Message resultMsg) { + public boolean onCreateWindow(WebView view, boolean isDialog, + boolean isUserGesture, Message resultMsg) { return false; } diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index f1c2bde..f240a2e 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -777,7 +777,7 @@ public class WebSettings { public void setDoubleTapZoom(int doubleTapZoom) { if (mDoubleTapZoom != doubleTapZoom) { mDoubleTapZoom = doubleTapZoom; - mWebView.updateDoubleTapZoom(); + mWebView.updateDoubleTapZoom(doubleTapZoom); } } diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java index 5ee1b8a..8aafc3d 100644 --- a/core/java/android/webkit/WebTextView.java +++ b/core/java/android/webkit/WebTextView.java @@ -16,14 +16,9 @@ package android.webkit; -import com.android.internal.widget.EditableInputConnection; - import android.content.Context; -import android.graphics.Canvas; import android.graphics.Color; -import android.graphics.ColorFilter; import android.graphics.Paint; -import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; @@ -60,12 +55,12 @@ import android.widget.ArrayAdapter; import android.widget.AutoCompleteTextView; import android.widget.TextView; +import junit.framework.Assert; + import java.net.MalformedURLException; import java.net.URL; import java.util.ArrayList; -import junit.framework.Assert; - /** * WebTextView is a specialized version of EditText used by WebView * to overlay html textfields (and textareas) to use our standard @@ -926,18 +921,23 @@ import junit.framework.Assert; */ /* package */ void setRect(int x, int y, int width, int height) { LayoutParams lp = (LayoutParams) getLayoutParams(); + boolean needsUpdate = false; if (null == lp) { lp = new LayoutParams(width, height, x, y); } else { - lp.x = x; - lp.y = y; - lp.width = width; - lp.height = height; + if ((lp.x != x) || (lp.y != y) || (lp.width != width) + || (lp.height != height)) { + needsUpdate = true; + lp.x = x; + lp.y = y; + lp.width = width; + lp.height = height; + } } if (getParent() == null) { // Insert the view so that it's drawn first (at index 0) mWebView.addView(this, 0, lp); - } else { + } else if (needsUpdate) { setLayoutParams(lp); } // Set up a measure spec so a layout can always be recreated. diff --git a/core/java/android/webkit/WebView.java b/core/java/android/webkit/WebView.java index fef9b02..55f345f 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -641,6 +641,7 @@ public class WebView extends AbsoluteLayout private boolean mAccessibilityScriptInjected; static final boolean USE_JAVA_TEXT_SELECTION = true; + static final boolean DEBUG_TEXT_HANDLES = false; private Region mTextSelectionRegion = new Region(); private Paint mTextSelectionPaint; private Drawable mSelectHandleLeft; @@ -682,7 +683,6 @@ public class WebView extends AbsoluteLayout private static final int SWITCH_TO_LONGPRESS = 4; private static final int RELEASE_SINGLE_TAP = 5; private static final int REQUEST_FORM_DATA = 6; - private static final int RESUME_WEBCORE_PRIORITY = 7; private static final int DRAG_HELD_MOTIONLESS = 8; private static final int AWAKEN_SCROLL_BARS = 9; private static final int PREVENT_DEFAULT_TIMEOUT = 10; @@ -2004,15 +2004,18 @@ public class WebView extends AbsoluteLayout } /** - * Load the given url with the extra headers. - * @param url The url of the resource to load. - * @param extraHeaders The extra headers sent with this url. This should not - * include the common headers like "user-agent". If it does, it - * will be replaced by the intrinsic value of the WebView. + * Load the given URL with the specified additional HTTP headers. + * @param url The URL of the resource to load. + * @param additionalHttpHeaders The additional headers to be used in the + * HTTP request for this URL, specified as a map from name to + * value. Note that if this map contains any of the headers + * that are set by default by the WebView, such as those + * controlling caching, accept types or the User-Agent, their + * values may be overriden by the WebView's defaults. */ - public void loadUrl(String url, Map<String, String> extraHeaders) { + public void loadUrl(String url, Map<String, String> additionalHttpHeaders) { checkThread(); - loadUrlImpl(url, extraHeaders); + loadUrlImpl(url, additionalHttpHeaders); } private void loadUrlImpl(String url, Map<String, String> extraHeaders) { @@ -2025,8 +2028,8 @@ public class WebView extends AbsoluteLayout } /** - * Load the given url. - * @param url The url of the resource to load. + * Load the given URL. + * @param url The URL of the resource to load. */ public void loadUrl(String url) { checkThread(); @@ -2846,46 +2849,47 @@ public class WebView extends AbsoluteLayout // Used to avoid sending many visible rect messages. private Rect mLastVisibleRectSent; private Rect mLastGlobalRect; + private Rect mVisibleRect = new Rect(); + private Rect mGlobalVisibleRect = new Rect(); + private Point mScrollOffset = new Point(); Rect sendOurVisibleRect() { if (mZoomManager.isPreventingWebkitUpdates()) return mLastVisibleRectSent; - Rect rect = new Rect(); - calcOurContentVisibleRect(rect); + calcOurContentVisibleRect(mVisibleRect); // Rect.equals() checks for null input. - if (!rect.equals(mLastVisibleRectSent)) { + if (!mVisibleRect.equals(mLastVisibleRectSent)) { if (!mBlockWebkitViewMessages) { - Point pos = new Point(rect.left, rect.top); + mScrollOffset.set(mVisibleRect.left, mVisibleRect.top); mWebViewCore.removeMessages(EventHub.SET_SCROLL_OFFSET); mWebViewCore.sendMessage(EventHub.SET_SCROLL_OFFSET, - nativeMoveGeneration(), mSendScrollEvent ? 1 : 0, pos); + nativeMoveGeneration(), mSendScrollEvent ? 1 : 0, mScrollOffset); } - mLastVisibleRectSent = rect; + mLastVisibleRectSent = mVisibleRect; mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); } - Rect globalRect = new Rect(); - if (getGlobalVisibleRect(globalRect) - && !globalRect.equals(mLastGlobalRect)) { + if (getGlobalVisibleRect(mGlobalVisibleRect) + && !mGlobalVisibleRect.equals(mLastGlobalRect)) { if (DebugFlags.WEB_VIEW) { - Log.v(LOGTAG, "sendOurVisibleRect=(" + globalRect.left + "," - + globalRect.top + ",r=" + globalRect.right + ",b=" - + globalRect.bottom); + Log.v(LOGTAG, "sendOurVisibleRect=(" + mGlobalVisibleRect.left + "," + + mGlobalVisibleRect.top + ",r=" + mGlobalVisibleRect.right + ",b=" + + mGlobalVisibleRect.bottom); } // TODO: the global offset is only used by windowRect() // in ChromeClientAndroid ; other clients such as touch // and mouse events could return view + screen relative points. if (!mBlockWebkitViewMessages) { - mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, globalRect); + mWebViewCore.sendMessage(EventHub.SET_GLOBAL_BOUNDS, mGlobalVisibleRect); } - mLastGlobalRect = globalRect; + mLastGlobalRect = mGlobalVisibleRect; } - return rect; + return mVisibleRect; } + private Point mGlobalVisibleOffset = new Point(); // Sets r to be the visible rectangle of our webview in view coordinates private void calcOurVisibleRect(Rect r) { - Point p = new Point(); - getGlobalVisibleRect(r, p); - r.offset(-p.x, -p.y); + getGlobalVisibleRect(r, mGlobalVisibleOffset); + r.offset(-mGlobalVisibleOffset.x, -mGlobalVisibleOffset.y); } // Sets r to be our visible rectangle in content coordinates @@ -2901,21 +2905,21 @@ public class WebView extends AbsoluteLayout r.bottom = viewToContentY(r.bottom); } + private Rect mContentVisibleRect = new Rect(); // Sets r to be our visible rectangle in content coordinates. We use this // method on the native side to compute the position of the fixed layers. // Uses floating coordinates (necessary to correctly place elements when // the scale factor is not 1) private void calcOurContentVisibleRectF(RectF r) { - Rect ri = new Rect(0,0,0,0); - calcOurVisibleRect(ri); - r.left = viewToContentXf(ri.left); + calcOurVisibleRect(mContentVisibleRect); + r.left = viewToContentXf(mContentVisibleRect.left); // viewToContentY will remove the total height of the title bar. Add // the visible height back in to account for the fact that if the title // bar is partially visible, the part of the visible rect which is // displaying our content is displaced by that amount. - r.top = viewToContentYf(ri.top + getVisibleTitleHeightImpl()); - r.right = viewToContentXf(ri.right); - r.bottom = viewToContentYf(ri.bottom); + r.top = viewToContentYf(mContentVisibleRect.top + getVisibleTitleHeightImpl()); + r.right = viewToContentXf(mContentVisibleRect.right); + r.bottom = viewToContentYf(mContentVisibleRect.bottom); } static class ViewSizeData { @@ -2992,8 +2996,8 @@ public class WebView extends AbsoluteLayout /** * Update the double-tap zoom. */ - /* package */ void updateDoubleTapZoom() { - mZoomManager.updateDoubleTapZoom(); + /* package */ void updateDoubleTapZoom(int doubleTapZoom) { + mZoomManager.updateDoubleTapZoom(doubleTapZoom); } private int computeRealHorizontalScrollRange() { @@ -3565,7 +3569,6 @@ public class WebView extends AbsoluteLayout mScrollingLayerRect.top = y; } abortAnimation(); - mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY); nativeSetIsScrolling(false); if (!mBlockWebkitViewMessages) { WebViewCore.resumePriority(); @@ -4115,20 +4118,6 @@ public class WebView extends AbsoluteLayout } private void drawContent(Canvas canvas, boolean drawRings) { - // Update the buttons in the picture, so when we draw the picture - // to the screen, they are in the correct state. - // Tell the native side if user is a) touching the screen, - // b) pressing the trackball down, or c) pressing the enter key - // If the cursor is on a button, we need to draw it in the pressed - // state. - // If mNativeClass is 0, we should not reach here, so we do not - // need to check it again. - boolean pressed = (mTouchMode == TOUCH_SHORTPRESS_START_MODE - || mTouchMode == TOUCH_INIT_MODE - || mTouchMode == TOUCH_SHORTPRESS_MODE); - recordButtons(canvas, - hasFocus() && hasWindowFocus(), (pressed && !USE_WEBKIT_RINGS) - || mTrackballDown || mGotCenterDown, false); drawCoreAndCursorRing(canvas, mBackgroundColor, mDrawCursorRing && drawRings); } @@ -4504,6 +4493,9 @@ public class WebView extends AbsoluteLayout if (mHeldMotionless == MOTIONLESS_FALSE) { mPrivateHandler.sendMessageDelayed(mPrivateHandler .obtainMessage(DRAG_HELD_MOTIONLESS), MOTIONLESS_TIME); + mPrivateHandler.sendMessageDelayed(mPrivateHandler + .obtainMessage(AWAKEN_SCROLL_BARS), + ViewConfiguration.getScrollDefaultDelay()); mHeldMotionless = MOTIONLESS_PENDING; } } @@ -4531,7 +4523,7 @@ public class WebView extends AbsoluteLayout int extras = DRAW_EXTRAS_NONE; if (mFindIsUp) { extras = DRAW_EXTRAS_FIND; - } else if (mSelectingText && !USE_JAVA_TEXT_SELECTION) { + } else if (mSelectingText && (!USE_JAVA_TEXT_SELECTION || DEBUG_TEXT_HANDLES)) { extras = DRAW_EXTRAS_SELECTION; nativeSetSelectionPointer(mNativeClass, mDrawSelectionPointer, @@ -4603,33 +4595,29 @@ public class WebView extends AbsoluteLayout mTextSelectionPaint.setColor(HIGHLIGHT_COLOR); } mTextSelectionRegion.setEmpty(); - nativeGetTextSelectionRegion(mTextSelectionRegion); + nativeGetTextSelectionRegion(mNativeClass, mTextSelectionRegion); Rect r = new Rect(); RegionIterator iter = new RegionIterator(mTextSelectionRegion); - int start_x = -1; - int start_y = -1; - int end_x = -1; - int end_y = -1; + Rect clip = canvas.getClipBounds(); while (iter.next(r)) { - r = new Rect( - contentToViewDimension(r.left), + r.set(contentToViewDimension(r.left), contentToViewDimension(r.top), contentToViewDimension(r.right), contentToViewDimension(r.bottom)); - // Regions are in order. First one is where selection starts, - // last one is where it ends - if (start_x < 0 || start_y < 0) { - start_x = r.left; - start_y = r.bottom; + if (r.intersect(clip)) { + canvas.drawRect(r, mTextSelectionPaint); } - end_x = r.right; - end_y = r.bottom; - canvas.drawRect(r, mTextSelectionPaint); } if (mSelectHandleLeft == null) { mSelectHandleLeft = mContext.getResources().getDrawable( com.android.internal.R.drawable.text_select_handle_left); } + int[] handles = new int[4]; + nativeGetSelectionHandles(mNativeClass, handles); + int start_x = contentToViewDimension(handles[0]); + int start_y = contentToViewDimension(handles[1]); + int end_x = contentToViewDimension(handles[2]); + int end_y = contentToViewDimension(handles[3]); // Magic formula copied from TextView start_x -= (mSelectHandleLeft.getIntrinsicWidth() * 3) / 4; mSelectHandleLeft.setBounds(start_x, start_y, @@ -4643,6 +4631,10 @@ public class WebView extends AbsoluteLayout mSelectHandleRight.setBounds(end_x, end_y, end_x + mSelectHandleRight.getIntrinsicWidth(), end_y + mSelectHandleRight.getIntrinsicHeight()); + if (DEBUG_TEXT_HANDLES) { + mSelectHandleLeft.setAlpha(125); + mSelectHandleRight.setAlpha(125); + } mSelectHandleLeft.draw(canvas); mSelectHandleRight.draw(canvas); } @@ -5186,9 +5178,6 @@ public class WebView extends AbsoluteLayout mGotCenterDown = true; mPrivateHandler.sendMessageDelayed(mPrivateHandler .obtainMessage(LONG_PRESS_CENTER), LONG_PRESS_TIMEOUT); - // Already checked mNativeClass, so we do not need to check it - // again. - recordButtons(null, hasFocus() && hasWindowFocus(), true, true); if (!wantsKeyEvents) return true; } // Bubble up the key event as WebView doesn't handle it @@ -5403,6 +5392,10 @@ public class WebView extends AbsoluteLayout } mExtendSelection = false; mSelectingText = mDrawSelectionPointer = true; + if (DEBUG_TEXT_HANDLES) { + // Debugging text handles requires running in software mode + setLayerType(LAYER_TYPE_SOFTWARE, null); + } // don't let the picture change during text selection WebViewCore.pauseUpdatePicture(mWebViewCore); if (nativeHasCursorNode()) { @@ -5481,6 +5474,11 @@ public class WebView extends AbsoluteLayout void selectionDone() { if (mSelectingText) { mSelectingText = false; + if (DEBUG_TEXT_HANDLES) { + // Debugging text handles required running in software mode, set + // back to default now + setLayerType(LAYER_TYPE_NONE, null); + } // finish is idempotent, so this is fine even if selectionDone was // called by mSelectCallback.onDestroyActionMode mSelectCallback.finish(); @@ -5615,9 +5613,6 @@ public class WebView extends AbsoluteLayout // drawing the cursor ring mDrawCursorRing = true; setFocusControllerActive(true); - if (mNativeClass != 0) { - recordButtons(null, true, false, true); - } } else { if (!inEditingMode()) { // If our window gained focus, but we do not have it, do not @@ -5643,9 +5638,6 @@ public class WebView extends AbsoluteLayout mKeysPressed.clear(); mPrivateHandler.removeMessages(SWITCH_TO_LONGPRESS); mTouchMode = TOUCH_DONE_MODE; - if (mNativeClass != 0) { - recordButtons(null, false, false, true); - } setFocusControllerActive(false); } invalidate(); @@ -5701,9 +5693,6 @@ public class WebView extends AbsoluteLayout // the cursor ring if (hasWindowFocus()) { mDrawCursorRing = true; - if (mNativeClass != 0) { - recordButtons(null, true, false, true); - } setFocusControllerActive(true); //} else { // The WebView has gained focus while we do not have @@ -5715,9 +5704,6 @@ public class WebView extends AbsoluteLayout // true if we are in editing mode), stop drawing the cursor ring. if (!inEditingMode()) { mDrawCursorRing = false; - if (mNativeClass != 0) { - recordButtons(null, false, false, true); - } setFocusControllerActive(false); } mKeysPressed.clear(); @@ -6005,7 +5991,7 @@ public class WebView extends AbsoluteLayout mScroller.abortAnimation(); mTouchMode = TOUCH_DRAG_START_MODE; mConfirmMove = true; - mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY); + nativeSetIsScrolling(false); } else if (mPrivateHandler.hasMessages(RELEASE_SINGLE_TAP)) { mPrivateHandler.removeMessages(RELEASE_SINGLE_TAP); if (USE_WEBKIT_RINGS || getSettings().supportTouchOnly()) { @@ -6288,9 +6274,16 @@ public class WebView extends AbsoluteLayout } mLastTouchX = x; mLastTouchY = y; - if ((deltaX | deltaY) != 0) { + + if (deltaX * deltaX + deltaY * deltaY > mTouchSlopSquare) { mHeldMotionless = MOTIONLESS_FALSE; + nativeSetIsScrolling(true); + } else { + mHeldMotionless = MOTIONLESS_TRUE; + nativeSetIsScrolling(false); + keepScrollBarsVisible = true; } + mLastTouchTime = eventTime; } @@ -6306,9 +6299,15 @@ public class WebView extends AbsoluteLayout // keep the scrollbar on the screen even there is no scroll awakenScrollBars(ViewConfiguration.getScrollDefaultDelay(), false); + // Post a message so that we'll keep them alive while we're not scrolling. + mPrivateHandler.sendMessageDelayed(mPrivateHandler + .obtainMessage(AWAKEN_SCROLL_BARS), + ViewConfiguration.getScrollDefaultDelay()); // return false to indicate that we can't pan out of the // view space return !done; + } else { + mPrivateHandler.removeMessages(AWAKEN_SCROLL_BARS); } break; } @@ -6817,7 +6816,6 @@ public class WebView extends AbsoluteLayout if (mNativeClass == 0) { return false; } - recordButtons(null, hasFocus() && hasWindowFocus(), true, true); if (time - mLastCursorTime <= TRACKBALL_TIMEOUT && !mLastCursorBounds.equals(nativeGetCursorRingBounds())) { nativeSelectBestAt(mLastCursorBounds); @@ -7329,7 +7327,6 @@ public class WebView extends AbsoluteLayout mLastTouchTime = eventTime; if (!mScroller.isFinished()) { abortAnimation(); - mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY); } mSnapScrollMode = SNAP_NONE; mVelocityTracker = VelocityTracker.obtain(); @@ -8461,10 +8458,6 @@ public class WebView extends AbsoluteLayout mWebTextView.setAdapterCustom(adapter); } break; - case RESUME_WEBCORE_PRIORITY: - WebViewCore.resumePriority(); - WebViewCore.resumeUpdatePicture(mWebViewCore); - break; case LONG_PRESS_CENTER: // as this is shared by keydown and trackballdown, reset all @@ -9412,24 +9405,6 @@ public class WebView extends AbsoluteLayout return nativeTileProfilingGetFloat(frame, tile, key); } - /** - * Helper method to deal with differences between hardware and software rendering - */ - private void recordButtons(Canvas canvas, boolean focus, boolean pressed, - boolean inval) { - boolean isHardwareAccel = canvas != null - ? canvas.isHardwareAccelerated() - : isHardwareAccelerated(); - if (isHardwareAccel) { - // We never want to change button state if we are hardware accelerated, - // but we DO want to invalidate as necessary so that the GL ring - // can be drawn - nativeRecordButtons(mNativeClass, false, false, inval); - } else { - nativeRecordButtons(mNativeClass, focus, pressed, inval); - } - } - private native int nativeCacheHitFramePointer(); private native boolean nativeCacheHitIsPlugin(); private native Rect nativeCacheHitNodeBounds(); @@ -9526,8 +9501,6 @@ public class WebView extends AbsoluteLayout private native boolean nativePointInNavCache(int x, int y, int slop); // Like many other of our native methods, you must make sure that // mNativeClass is not null before calling this method. - private native void nativeRecordButtons(int nativeInstance, - boolean focused, boolean pressed, boolean invalidate); private native void nativeResetSelection(); private native Point nativeSelectableText(); private native void nativeSelectAll(); @@ -9588,7 +9561,8 @@ public class WebView extends AbsoluteLayout private native int nativeGetBackgroundColor(); native boolean nativeSetProperty(String key, String value); native String nativeGetProperty(String key); - private native void nativeGetTextSelectionRegion(Region region); + private native void nativeGetTextSelectionRegion(int instance, Region region); + private native void nativeGetSelectionHandles(int instance, int[] handles); /** * See {@link ComponentCallbacks2} for the trim levels and descriptions */ diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 1294a28..754d6e9 100644 --- a/core/java/android/webkit/WebViewCore.java +++ b/core/java/android/webkit/WebViewCore.java @@ -124,6 +124,7 @@ public final class WebViewCore { */ private int mViewportDensityDpi = -1; + private boolean mIsRestored = false; private float mRestoredScale = 0; private float mRestoredTextWrapScale = 0; private int mRestoredX = 0; @@ -2254,7 +2255,7 @@ public final class WebViewCore { if (mWebView == null) return; - boolean updateViewState = standardLoad || mRestoredScale > 0; + boolean updateViewState = standardLoad || mIsRestored; setupViewport(updateViewState); // if updateRestoreState is true, ViewManager.postReadyToDrawAll() will // be called after the WebView updates its state. If updateRestoreState @@ -2271,6 +2272,7 @@ public final class WebViewCore { // reset the scroll position, the restored offset and scales mRestoredX = mRestoredY = 0; + mIsRestored = false; mRestoredScale = mRestoredTextWrapScale = 0; } @@ -2288,6 +2290,18 @@ public final class WebViewCore { // set the viewport settings from WebKit setViewportSettingsFromNative(); + // clamp initial scale + if (mViewportInitialScale > 0) { + if (mViewportMinimumScale > 0) { + mViewportInitialScale = Math.max(mViewportInitialScale, + mViewportMinimumScale); + } + if (mViewportMaximumScale > 0) { + mViewportInitialScale = Math.min(mViewportInitialScale, + mViewportMaximumScale); + } + } + if (mSettings.forceUserScalable()) { mViewportUserScalable = true; if (mViewportInitialScale > 0) { @@ -2399,7 +2413,7 @@ public final class WebViewCore { mInitialViewState.mScrollX = mRestoredX; mInitialViewState.mScrollY = mRestoredY; mInitialViewState.mMobileSite = (0 == mViewportWidth); - if (mRestoredScale > 0) { + if (mIsRestored) { mInitialViewState.mIsRestored = true; mInitialViewState.mViewScale = mRestoredScale; if (mRestoredTextWrapScale > 0) { @@ -2525,13 +2539,10 @@ public final class WebViewCore { // called by JNI private void restoreScale(float scale, float textWrapScale) { if (mBrowserFrame.firstLayoutDone() == false) { - // If restored scale and textWrapScale are 0, set them to - // overview and reading level scale respectively. - mRestoredScale = (scale <= 0.0) - ? mWebView.getZoomOverviewScale() : scale; + mIsRestored = scale > 0; + mRestoredScale = scale; if (mSettings.getUseWideViewPort()) { - mRestoredTextWrapScale = (textWrapScale <= 0.0) - ? mWebView.getReadingLevelScale() : textWrapScale; + mRestoredTextWrapScale = textWrapScale; } } } diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java index 9151fdd..f599dba 100644 --- a/core/java/android/webkit/ZoomManager.java +++ b/core/java/android/webkit/ZoomManager.java @@ -152,6 +152,12 @@ class ZoomManager { private float mDisplayDensity; /* + * The factor that is used to tweak the zoom scale on a double-tap, + * and can be changed via WebSettings. Range is from 0.75f to 1.25f. + */ + private float mDoubleTapZoomFactor = 1.0f; + + /* * The scale factor that is used as the minimum increment when going from * overview to reading level on a double tap. */ @@ -163,11 +169,7 @@ class ZoomManager { /* * The initial scale for the WebView. 0 means default. If initial scale is - * greater than 0 the WebView starts with this value as its initial scale. The - * value is converted from an integer percentage so it is guarenteed to have - * no more than 2 significant digits after the decimal. This restriction - * allows us to convert the scale back to the original percentage by simply - * multiplying the value by 100. + * greater than 0, the WebView starts with this value as its initial scale. */ private float mInitialScale; @@ -307,17 +309,14 @@ class ZoomManager { } public final float getDefaultScale() { - return mInitialScale > 0 ? mInitialScale : mDefaultScale; + return mDefaultScale; } /** * Returns the zoom scale used for reading text on a double-tap. */ public final float getReadingLevelScale() { - WebSettings settings = mWebView.getSettings(); - final float doubleTapZoomFactor = settings != null - ? settings.getDoubleTapZoom() / 100.f : 1.0f; - return mDisplayDensity * doubleTapZoomFactor; + return mDisplayDensity * mDoubleTapZoomFactor; } public final float getInvDefaultScale() { @@ -350,9 +349,7 @@ class ZoomManager { } public final void setInitialScaleInPercent(int scaleInPercent) { - mInitialScale = scaleInPercent * 0.01f; - mActualScale = mInitialScale > 0 ? mInitialScale : mDefaultScale; - mInvActualScale = 1 / mActualScale; + mInitialScale = scaleInPercent * mDisplayDensity * 0.01f; } public final float computeScaleWithLimits(float scale) { @@ -516,8 +513,9 @@ class ZoomManager { return mZoomScale != 0 || mInHWAcceleratedZoom; } - public void updateDoubleTapZoom() { + public void updateDoubleTapZoom(int doubleTapZoom) { if (mInZoomOverview) { + mDoubleTapZoomFactor = doubleTapZoom / 100.0f; mTextWrapScale = getReadingLevelScale(); refreshZoomScale(true); } @@ -1116,10 +1114,11 @@ class ZoomManager { float scale; if (mInitialScale > 0) { scale = mInitialScale; - mTextWrapScale = scale; - } else if (viewState.mViewScale > 0) { - mTextWrapScale = viewState.mTextWrapScale; - scale = viewState.mViewScale; + } else if (viewState.mIsRestored || viewState.mViewScale > 0) { + scale = (viewState.mViewScale > 0) + ? viewState.mViewScale : overviewScale; + mTextWrapScale = (viewState.mTextWrapScale > 0) + ? viewState.mTextWrapScale : getReadingLevelScale(); } else { scale = overviewScale; if (!settings.getUseWideViewPort() @@ -1135,7 +1134,7 @@ class ZoomManager { } boolean reflowText = false; if (!viewState.mIsRestored) { - if (settings.getUseFixedViewport() && mInitialScale == 0) { + if (settings.getUseFixedViewport()) { // Override the scale only in case of fixed viewport. scale = Math.max(scale, overviewScale); mTextWrapScale = Math.max(mTextWrapScale, overviewScale); diff --git a/core/java/android/widget/CalendarView.java b/core/java/android/widget/CalendarView.java index 9cbe8db..e0403ff 100644 --- a/core/java/android/widget/CalendarView.java +++ b/core/java/android/widget/CalendarView.java @@ -16,8 +16,6 @@ package android.widget; -import com.android.internal.R; - import android.annotation.Widget; import android.app.Service; import android.content.Context; @@ -31,7 +29,6 @@ import android.graphics.Paint.Style; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.text.TextUtils; -import android.text.format.DateFormat; import android.text.format.DateUtils; import android.util.AttributeSet; import android.util.DisplayMetrics; @@ -44,6 +41,8 @@ import android.view.View; import android.view.ViewGroup; import android.widget.AbsListView.OnScrollListener; +import com.android.internal.R; + import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Calendar; @@ -121,11 +120,6 @@ public class CalendarView extends FrameLayout { private static final int SCROLL_CHANGE_DELAY = 40; /** - * String for formatting the month name in the title text view. - */ - private static final String FORMAT_MONTH_NAME = "MMMM, yyyy"; - - /** * String for parsing dates. */ private static final String DATE_FORMAT = "MM/dd/yyyy"; @@ -940,11 +934,17 @@ public class CalendarView extends FrameLayout { * @param calendar A day in the new focus month. */ private void setMonthDisplayed(Calendar calendar) { - mMonthName.setText(DateFormat.format(FORMAT_MONTH_NAME, calendar)); - mMonthName.invalidate(); - mCurrentMonthDisplayed = calendar.get(Calendar.MONTH); - mAdapter.setFocusMonth(mCurrentMonthDisplayed); - // TODO Send Accessibility Event + final int newMonthDisplayed = calendar.get(Calendar.MONTH); + if (mCurrentMonthDisplayed != newMonthDisplayed) { + mCurrentMonthDisplayed = newMonthDisplayed; + mAdapter.setFocusMonth(mCurrentMonthDisplayed); + final int flags = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_NO_MONTH_DAY + | DateUtils.FORMAT_SHOW_YEAR; + final long millis = calendar.getTimeInMillis(); + String newMonthName = DateUtils.formatDateRange(mContext, millis, millis, flags); + mMonthName.setText(newMonthName); + mMonthName.invalidate(); + } } /** diff --git a/core/java/android/widget/GridLayout.java b/core/java/android/widget/GridLayout.java index fb9047b..7cf5168 100644 --- a/core/java/android/widget/GridLayout.java +++ b/core/java/android/widget/GridLayout.java @@ -867,16 +867,18 @@ public class GridLayout extends ViewGroup { if (firstPass) { measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, lp.height); } else { - Spec spec = (orientation == HORIZONTAL) ? lp.columnSpec : lp.rowSpec; + boolean horizontal = (orientation == HORIZONTAL); + Spec spec = horizontal ? lp.columnSpec : lp.rowSpec; if (spec.alignment == FILL) { Interval span = spec.span; - Axis axis = (orientation == HORIZONTAL) ? horizontalAxis : verticalAxis; + Axis axis = horizontal ? horizontalAxis : verticalAxis; int[] locations = axis.getLocations(); - int size = locations[span.max] - locations[span.min]; - if (orientation == HORIZONTAL) { - measureChildWithMargins2(c, widthSpec, heightSpec, size, lp.height); + int cellSize = locations[span.max] - locations[span.min]; + int viewSize = cellSize - getTotalMargin(c, horizontal); + if (horizontal) { + measureChildWithMargins2(c, widthSpec, heightSpec, viewSize, lp.height); } else { - measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, size); + measureChildWithMargins2(c, widthSpec, heightSpec, lp.width, viewSize); } } } diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index 57701ae..1683d20 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -697,6 +697,11 @@ public class HorizontalScrollView extends FrameLayout { } @Override + public boolean shouldDelayChildPressedState() { + return true; + } + + @Override protected void onOverScrolled(int scrollX, int scrollY, boolean clampedX, boolean clampedY) { // Treat animating scrolls differently; see #computeScroll() for why. diff --git a/core/java/android/widget/ListView.java b/core/java/android/widget/ListView.java index 9ef1aa1..7f7a3a7 100644 --- a/core/java/android/widget/ListView.java +++ b/core/java/android/widget/ListView.java @@ -3361,6 +3361,7 @@ public class ListView extends AbsListView { final ListAdapter adapter = mAdapter; int closetChildIndex = -1; + int closestChildTop = 0; if (adapter != null && gainFocus && previouslyFocusedRect != null) { previouslyFocusedRect.offset(mScrollX, mScrollY); @@ -3392,12 +3393,13 @@ public class ListView extends AbsListView { if (distance < minDistance) { minDistance = distance; closetChildIndex = i; + closestChildTop = other.getTop(); } } } if (closetChildIndex >= 0) { - setSelection(closetChildIndex + mFirstPosition); + setSelectionFromTop(closetChildIndex + mFirstPosition, closestChildTop); } else { requestLayout(); } diff --git a/core/java/android/widget/NumberPicker.java b/core/java/android/widget/NumberPicker.java index cf015c4..1a1b8d0 100644 --- a/core/java/android/widget/NumberPicker.java +++ b/core/java/android/widget/NumberPicker.java @@ -203,6 +203,31 @@ public class NumberPicker extends LinearLayout { private final EditText mInputText; /** + * The min height of this widget. + */ + private final int mMinHeight; + + /** + * The max height of this widget. + */ + private final int mMaxHeight; + + /** + * The max width of this widget. + */ + private final int mMinWidth; + + /** + * The max width of this widget. + */ + private int mMaxWidth; + + /** + * Flag whether to compute the max width. + */ + private final boolean mComputeMaxWidth; + + /** * The height of the text. */ private final int mTextSize; @@ -517,6 +542,19 @@ public class NumberPicker extends LinearLayout { getResources().getDisplayMetrics()); mSelectionDividerHeight = attributesArray.getDimensionPixelSize( R.styleable.NumberPicker_selectionDividerHeight, defSelectionDividerHeight); + mMinHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_minHeight, 0); + mMaxHeight = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxHeight, + Integer.MAX_VALUE); + if (mMinHeight > mMaxHeight) { + throw new IllegalArgumentException("minHeight > maxHeight"); + } + mMinWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_minWidth, 0); + mMaxWidth = attributesArray.getDimensionPixelSize(R.styleable.NumberPicker_maxWidth, + Integer.MAX_VALUE); + if (mMinWidth > mMaxWidth) { + throw new IllegalArgumentException("minWidth > maxWidth"); + } + mComputeMaxWidth = (mMaxWidth == Integer.MAX_VALUE); attributesArray.recycle(); mShowInputControlsAnimimationDuration = getResources().getInteger( @@ -665,7 +703,34 @@ public class NumberPicker extends LinearLayout { @Override protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - super.onLayout(changed, left, top, right, bottom); + final int msrdWdth = getMeasuredWidth(); + final int msrdHght = getMeasuredHeight(); + + // Increment button at the top. + final int inctBtnMsrdWdth = mIncrementButton.getMeasuredWidth(); + final int incrBtnLeft = (msrdWdth - inctBtnMsrdWdth) / 2; + final int incrBtnTop = 0; + final int incrBtnRight = incrBtnLeft + inctBtnMsrdWdth; + final int incrBtnBottom = incrBtnTop + mIncrementButton.getMeasuredHeight(); + mIncrementButton.layout(incrBtnLeft, incrBtnTop, incrBtnRight, incrBtnBottom); + + // Input text centered horizontally. + final int inptTxtMsrdWdth = mInputText.getMeasuredWidth(); + final int inptTxtMsrdHght = mInputText.getMeasuredHeight(); + final int inptTxtLeft = (msrdWdth - inptTxtMsrdWdth) / 2; + final int inptTxtTop = (msrdHght - inptTxtMsrdHght) / 2; + final int inptTxtRight = inptTxtLeft + inptTxtMsrdWdth; + final int inptTxtBottom = inptTxtTop + inptTxtMsrdHght; + mInputText.layout(inptTxtLeft, inptTxtTop, inptTxtRight, inptTxtBottom); + + // Decrement button at the top. + final int decrBtnMsrdWdth = mIncrementButton.getMeasuredWidth(); + final int decrBtnLeft = (msrdWdth - decrBtnMsrdWdth) / 2; + final int decrBtnTop = msrdHght - mDecrementButton.getMeasuredHeight(); + final int decrBtnRight = decrBtnLeft + decrBtnMsrdWdth; + final int decrBtnBottom = msrdHght; + mDecrementButton.layout(decrBtnLeft, decrBtnTop, decrBtnRight, decrBtnBottom); + if (!mScrollWheelAndFadingEdgesInitialized) { mScrollWheelAndFadingEdgesInitialized = true; // need to do all this when we know our size @@ -675,6 +740,20 @@ public class NumberPicker extends LinearLayout { } @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + // Try greedily to fit the max width and height. + final int newWidthMeasureSpec = makeMeasureSpec(widthMeasureSpec, mMaxWidth); + final int newHeightMeasureSpec = makeMeasureSpec(heightMeasureSpec, mMaxHeight); + super.onMeasure(newWidthMeasureSpec, newHeightMeasureSpec); + // Flag if we are measured with width or height less than the respective min. + final int desiredWidth = Math.max(mMinWidth, getMeasuredWidth()); + final int desiredHeight = Math.max(mMinHeight, getMeasuredHeight()); + final int widthSize = resolveSizeAndState(desiredWidth, newWidthMeasureSpec, 0); + final int heightSize = resolveSizeAndState(desiredHeight, newHeightMeasureSpec, 0); + setMeasuredDimension(widthSize, heightSize); + } + + @Override public boolean onInterceptTouchEvent(MotionEvent event) { if (!isEnabled() || !mFlingable) { return false; @@ -700,17 +779,14 @@ public class NumberPicker extends LinearLayout { hideInputControls(); return true; } - if (isEventInViewHitRect(event, mInputText) - || (!mIncrementButton.isShown() - && isEventInViewHitRect(event, mIncrementButton)) - || (!mDecrementButton.isShown() - && isEventInViewHitRect(event, mDecrementButton))) { - mAdjustScrollerOnUpEvent = false; - setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE); - hideInputControls(); - return true; + if (isEventInVisibleViewHitRect(event, mIncrementButton) + || isEventInVisibleViewHitRect(event, mDecrementButton)) { + return false; } - break; + mAdjustScrollerOnUpEvent = false; + setSelectorWheelState(SELECTOR_WHEEL_STATE_LARGE); + hideInputControls(); + return true; case MotionEvent.ACTION_MOVE: float currentMoveY = event.getY(); int deltaDownY = (int) Math.abs(currentMoveY - mLastDownEventY); @@ -976,6 +1052,49 @@ public class NumberPicker extends LinearLayout { } /** + * Computes the max width if no such specified as an attribute. + */ + private void tryComputeMaxWidth() { + if (!mComputeMaxWidth) { + return; + } + int maxTextWidth = 0; + if (mDisplayedValues == null) { + float maxDigitWidth = 0; + for (int i = 0; i <= 9; i++) { + final float digitWidth = mSelectorWheelPaint.measureText(String.valueOf(i)); + if (digitWidth > maxDigitWidth) { + maxDigitWidth = digitWidth; + } + } + int numberOfDigits = 0; + int current = mMaxValue; + while (current > 0) { + numberOfDigits++; + current = current / 10; + } + maxTextWidth = (int) (numberOfDigits * maxDigitWidth); + } else { + final int valueCount = mDisplayedValues.length; + for (int i = 0; i < valueCount; i++) { + final float textWidth = mSelectorWheelPaint.measureText(mDisplayedValues[i]); + if (textWidth > maxTextWidth) { + maxTextWidth = (int) textWidth; + } + } + } + maxTextWidth += mInputText.getPaddingLeft() + mInputText.getPaddingRight(); + if (mMaxWidth != maxTextWidth) { + if (maxTextWidth > mMinWidth) { + mMaxWidth = maxTextWidth; + } else { + mMaxWidth = mMinWidth; + } + invalidate(); + } + } + + /** * Gets whether the selector wheel wraps when reaching the min/max value. * * @return True if the selector wheel wraps. @@ -1061,6 +1180,7 @@ public class NumberPicker extends LinearLayout { setWrapSelectorWheel(wrapSelectorWheel); initializeSelectorWheelIndices(); updateInputTextView(); + tryComputeMaxWidth(); } /** @@ -1092,6 +1212,7 @@ public class NumberPicker extends LinearLayout { setWrapSelectorWheel(wrapSelectorWheel); initializeSelectorWheelIndices(); updateInputTextView(); + tryComputeMaxWidth(); } /** @@ -1240,6 +1361,28 @@ public class NumberPicker extends LinearLayout { } /** + * Makes a measure spec that tries greedily to use the max value. + * + * @param measureSpec The measure spec. + * @param maxSize The max value for the size. + * @return A measure spec greedily imposing the max size. + */ + private int makeMeasureSpec(int measureSpec, int maxSize) { + final int size = MeasureSpec.getSize(measureSpec); + final int mode = MeasureSpec.getMode(measureSpec); + switch (mode) { + case MeasureSpec.EXACTLY: + return measureSpec; + case MeasureSpec.AT_MOST: + return MeasureSpec.makeMeasureSpec(Math.min(size, maxSize), MeasureSpec.EXACTLY); + case MeasureSpec.UNSPECIFIED: + return MeasureSpec.makeMeasureSpec(maxSize, MeasureSpec.EXACTLY); + default: + throw new IllegalArgumentException("Unknown measure mode: " + mode); + } + } + + /** * Resets the selector indices and clear the cached * string representation of these indices. */ @@ -1335,11 +1478,14 @@ public class NumberPicker extends LinearLayout { } /** - * @return If the <code>event</code> is in the <code>view</code>. + * @return If the <code>event</code> is in the visible <code>view</code>. */ - private boolean isEventInViewHitRect(MotionEvent event, View view) { - view.getHitRect(mTempRect); - return mTempRect.contains((int) event.getX(), (int) event.getY()); + private boolean isEventInVisibleViewHitRect(MotionEvent event, View view) { + if (view.getVisibility() == VISIBLE) { + view.getHitRect(mTempRect); + return mTempRect.contains((int) event.getX(), (int) event.getY()); + } + return false; } /** diff --git a/core/java/android/widget/RemoteViews.java b/core/java/android/widget/RemoteViews.java index 9cf2718..1592061 100644 --- a/core/java/android/widget/RemoteViews.java +++ b/core/java/android/widget/RemoteViews.java @@ -1546,6 +1546,16 @@ public class RemoteViews implements Parcelable, Filter { } /** + * Equivalent to calling View.setContentDescription + * + * @param viewId The id of the view whose content description should change + * @param contentDescription The new content description for the view + */ + public void setContentDescription(int viewId, CharSequence contentDescription) { + setCharSequence(viewId, "setContentDescription", contentDescription); + } + + /** * Inflates the view hierarchy represented by this object and applies * all of the actions. * diff --git a/core/java/android/widget/SearchView.java b/core/java/android/widget/SearchView.java index 6df80e4..f524ef0 100644 --- a/core/java/android/widget/SearchView.java +++ b/core/java/android/widget/SearchView.java @@ -151,6 +151,14 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } }; + private Runnable mReleaseCursorRunnable = new Runnable() { + public void run() { + if (mSuggestionsAdapter != null && mSuggestionsAdapter instanceof SuggestionsAdapter) { + mSuggestionsAdapter.changeCursor(null); + } + } + }; + // For voice searching private final Intent mVoiceWebSearchIntent; private final Intent mVoiceAppSearchIntent; @@ -720,7 +728,8 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { private void updateSubmitButton(boolean hasText) { int visibility = GONE; - if (isSubmitAreaEnabled() && hasFocus() && (hasText || !mVoiceButtonEnabled)) { + if (mSubmitButtonEnabled && isSubmitAreaEnabled() && hasFocus() + && (hasText || !mVoiceButtonEnabled)) { visibility = VISIBLE; } mSubmitButton.setVisibility(visibility); @@ -759,6 +768,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { @Override protected void onDetachedFromWindow() { removeCallbacks(mUpdateDrawableStateRunnable); + post(mReleaseCursorRunnable); super.onDetachedFromWindow(); } @@ -1028,7 +1038,9 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { } } mQueryTextView.setInputType(inputType); - + if (mSuggestionsAdapter != null) { + mSuggestionsAdapter.changeCursor(null); + } // attach the suggestions adapter, if suggestions are available // The existence of a suggestions authority is the proxy for "suggestions available here" if (mSearchable.getSuggestAuthority() != null) { @@ -1071,9 +1083,7 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { CharSequence text = mQueryTextView.getText(); mUserQuery = text; boolean hasText = !TextUtils.isEmpty(text); - if (isSubmitButtonEnabled()) { - updateSubmitButton(hasText); - } + updateSubmitButton(hasText); updateVoiceButton(!hasText); updateCloseButton(); updateSubmitArea(); @@ -1177,7 +1187,6 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { public void onActionViewCollapsed() { clearFocus(); updateViewsVisibility(true); - mQueryTextView.setText(""); mQueryTextView.setImeOptions(mCollapsedImeOptions); mExpandedInActionView = false; } @@ -1187,9 +1196,12 @@ public class SearchView extends LinearLayout implements CollapsibleActionView { */ @Override public void onActionViewExpanded() { + if (mExpandedInActionView) return; + mExpandedInActionView = true; mCollapsedImeOptions = mQueryTextView.getImeOptions(); mQueryTextView.setImeOptions(mCollapsedImeOptions | EditorInfo.IME_FLAG_NO_FULLSCREEN); + mQueryTextView.setText(""); setIconified(false); } diff --git a/core/java/android/widget/SpellChecker.java b/core/java/android/widget/SpellChecker.java index 510e2d4..87c3e9b 100644 --- a/core/java/android/widget/SpellChecker.java +++ b/core/java/android/widget/SpellChecker.java @@ -32,6 +32,7 @@ import android.view.textservice.TextServicesManager; import com.android.internal.util.ArrayUtils; import java.text.BreakIterator; +import java.util.Locale; /** @@ -45,7 +46,7 @@ public class SpellChecker implements SpellCheckerSessionListener { private final TextView mTextView; - final SpellCheckerSession mSpellCheckerSession; + SpellCheckerSession mSpellCheckerSession; final int mCookie; // Paired arrays for the (id, spellCheckSpan) pair. A negative id means the associated @@ -61,23 +62,55 @@ public class SpellChecker implements SpellCheckerSessionListener { private int mSpanSequenceCounter = 0; + private Locale mCurrentLocale; + + // Shared by all SpellParsers. Cannot be shared with TextView since it may be used + // concurrently due to the asynchronous nature of onGetSuggestions. + private WordIterator mWordIterator; + public SpellChecker(TextView textView) { mTextView = textView; - final TextServicesManager textServicesManager = (TextServicesManager) textView.getContext(). - getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); - mSpellCheckerSession = textServicesManager.newSpellCheckerSession( - null /* not currently used by the textServicesManager */, - null /* null locale means use the languages defined in Settings - if referToSpellCheckerLanguageSettings is true */, - this, true /* means use the languages defined in Settings */); - mCookie = hashCode(); - - // Arbitrary: 4 simultaneous spell check spans. Will automatically double size on demand + // Arbitrary: these arrays will automatically double their sizes on demand final int size = ArrayUtils.idealObjectArraySize(1); mIds = new int[size]; mSpellCheckSpans = new SpellCheckSpan[size]; + + setLocale(mTextView.getLocale()); + + mCookie = hashCode(); + } + + private void setLocale(Locale locale) { + closeSession(); + + final TextServicesManager textServicesManager = (TextServicesManager) + mTextView.getContext().getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + if (!textServicesManager.isSpellCheckerEnabled()) { + mSpellCheckerSession = null; + } else { + mSpellCheckerSession = textServicesManager.newSpellCheckerSession( + null /* Bundle not currently used by the textServicesManager */, + locale, this, + false /* means any available languages from current spell checker */); + } + mCurrentLocale = locale; + + // Restore SpellCheckSpans in pool + for (int i = 0; i < mLength; i++) { + mSpellCheckSpans[i].setSpellCheckInProgress(false); + mIds[i] = -1; + } mLength = 0; + + // Change SpellParsers' wordIterator locale + mWordIterator = new WordIterator(locale); + + // Remove existing misspelled SuggestionSpans + mTextView.removeMisspelledSpans((Editable) mTextView.getText()); + + // This class is the listener for locale change: warn other locale-aware objects + mTextView.onLocaleChanged(); } /** @@ -95,7 +128,7 @@ public class SpellChecker implements SpellCheckerSessionListener { final int length = mSpellParsers.length; for (int i = 0; i < length; i++) { - mSpellParsers[i].close(); + mSpellParsers[i].finish(); } } @@ -140,12 +173,20 @@ public class SpellChecker implements SpellCheckerSessionListener { } public void spellCheck(int start, int end) { + final Locale locale = mTextView.getLocale(); + if (mCurrentLocale == null || (!(mCurrentLocale.equals(locale)))) { + setLocale(locale); + // Re-check the entire text + start = 0; + end = mTextView.getText().length(); + } + if (!isSessionActive()) return; final int length = mSpellParsers.length; for (int i = 0; i < length; i++) { final SpellParser spellParser = mSpellParsers[i]; - if (spellParser.isDone()) { + if (spellParser.isFinished()) { spellParser.init(start, end); spellParser.parse(); return; @@ -229,7 +270,7 @@ public class SpellChecker implements SpellCheckerSessionListener { final int length = mSpellParsers.length; for (int i = 0; i < length; i++) { final SpellParser spellParser = mSpellParsers[i]; - if (!spellParser.isDone()) { + if (!spellParser.isFinished()) { spellParser.parse(); } } @@ -239,6 +280,7 @@ public class SpellChecker implements SpellCheckerSessionListener { SuggestionsInfo suggestionsInfo, SpellCheckSpan spellCheckSpan) { final int start = editable.getSpanStart(spellCheckSpan); final int end = editable.getSpanEnd(spellCheckSpan); + if (start < 0 || end < 0) return; // span was removed in the meantime // Other suggestion spans may exist on that region, with identical suggestions, filter // them out to avoid duplicates. First, filter suggestion spans on that exact region. @@ -249,7 +291,6 @@ public class SpellChecker implements SpellCheckerSessionListener { final int spanEnd = editable.getSpanEnd(suggestionSpans[i]); if (spanStart != start || spanEnd != end) { suggestionSpans[i] = null; - break; } } @@ -301,7 +342,6 @@ public class SpellChecker implements SpellCheckerSessionListener { } private class SpellParser { - private WordIterator mWordIterator = new WordIterator(/*TODO Locale*/); private Object mRange = new Object(); public void init(int start, int end) { @@ -309,11 +349,11 @@ public class SpellChecker implements SpellCheckerSessionListener { Spanned.SPAN_EXCLUSIVE_EXCLUSIVE); } - public void close() { + public void finish() { ((Editable) mTextView.getText()).removeSpan(mRange); } - public boolean isDone() { + public boolean isFinished() { return ((Editable) mTextView.getText()).getSpanStart(mRange) < 0; } diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index 27d44bf..ec3790e 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -68,6 +68,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { int mDropDownWidth; private int mGravity; + private boolean mDisableChildrenWhenDisabled; private Rect mTempRect = new Rect(); @@ -186,6 +187,9 @@ public class Spinner extends AbsSpinner implements OnClickListener { mPopup.setPromptText(a.getString(com.android.internal.R.styleable.Spinner_prompt)); + mDisableChildrenWhenDisabled = a.getBoolean( + com.android.internal.R.styleable.Spinner_disableChildrenWhenDisabled, false); + a.recycle(); // Base constructor can call setAdapter before we initialize mPopup. @@ -196,6 +200,17 @@ public class Spinner extends AbsSpinner implements OnClickListener { } } + @Override + public void setEnabled(boolean enabled) { + super.setEnabled(enabled); + if (mDisableChildrenWhenDisabled) { + final int count = getChildCount(); + for (int i = 0; i < count; i++) { + getChildAt(i).setEnabled(enabled); + } + } + } + /** * Describes how the selected item view is positioned. Currently only the horizontal component * is used. The default is determined by the current theme. @@ -398,6 +413,9 @@ public class Spinner extends AbsSpinner implements OnClickListener { addViewInLayout(child, 0, lp); child.setSelected(hasFocus()); + if (mDisableChildrenWhenDisabled) { + child.setEnabled(isEnabled()); + } // Get measure specs int childHeightSpec = ViewGroup.getChildMeasureSpec(mHeightMeasureSpec, diff --git a/core/java/android/widget/SuggestionsAdapter.java b/core/java/android/widget/SuggestionsAdapter.java index 9e32c9a..c44d431 100644 --- a/core/java/android/widget/SuggestionsAdapter.java +++ b/core/java/android/widget/SuggestionsAdapter.java @@ -29,9 +29,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.ColorStateList; import android.content.res.Resources; import android.database.Cursor; -import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.StateListDrawable; import android.net.Uri; import android.os.Bundle; import android.text.Spannable; @@ -39,7 +37,6 @@ import android.text.SpannableString; import android.text.TextUtils; import android.text.style.TextAppearanceSpan; import android.util.Log; -import android.util.SparseArray; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; @@ -113,7 +110,6 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene mOutsideDrawablesCache = outsideDrawablesCache; - // mStartSpinnerRunnable = new Runnable() { // public void run() { // // mSearchView.setWorking(true); // TODO: @@ -185,6 +181,10 @@ class SuggestionsAdapter extends ResourceCursorAdapter implements OnClickListene * the results. */ Cursor cursor = null; + if (mSearchView.getVisibility() != View.VISIBLE + || mSearchView.getWindowVisibility() != View.VISIBLE) { + return null; + } //mSearchView.getWindow().getDecorView().post(mStartSpinnerRunnable); // TODO: try { cursor = mSearchManager.getSuggestions(mSearchable, query, QUERY_LIMIT); diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index 0a2365e..5833afd 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -132,6 +132,7 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.view.inputmethod.InputMethodSubtype; import android.widget.AdapterView.OnItemClickListener; import android.widget.RemoteViews.RemoteView; @@ -147,6 +148,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Comparator; import java.util.HashMap; +import java.util.Locale; /** * Displays text to the user and optionally allows them to edit it. A TextView @@ -269,7 +271,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static final int SIGNED = 2; private static final int DECIMAL = 4; - class Drawables { + static class Drawables { final Rect mCompoundRect = new Rect(); Drawable mDrawableTop, mDrawableBottom, mDrawableLeft, mDrawableRight, mDrawableStart, mDrawableEnd; @@ -302,7 +304,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private int mMarqueeRepeatLimit = 3; - class InputContentType { + static class InputContentType { int imeOptions = EditorInfo.IME_NULL; String privateImeOptions; CharSequence imeActionLabel; @@ -313,7 +315,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } InputContentType mInputContentType; - class InputMethodState { + static class InputMethodState { Rect mCursorRectInWindow = new Rect(); RectF mTmpRectF = new RectF(); float[] mTmpOffset = new float[2]; @@ -357,6 +359,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private SpellChecker mSpellChecker; + private boolean mSoftInputShownOnFocus = true; + // The alignment to pass to Layout, or null if not resolved. private Layout.Alignment mLayoutAlignment; @@ -605,6 +609,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener mLinksClickable = a.getBoolean(attr, true); break; +// TODO uncomment when this attribute is made public in the next release +// also add TextView_showSoftInputOnFocus to the list of attributes above +// case com.android.internal.R.styleable.TextView_showSoftInputOnFocus: +// setShowSoftInputOnFocus(a.getBoolean(attr, true)); +// break; + case com.android.internal.R.styleable.TextView_drawableLeft: drawableLeft = a.getDrawable(attr); break; @@ -2368,6 +2378,29 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } /** + * Sets whether the soft input method will be made visible when this + * TextView gets focused. The default is true. + * + * @attr ref android.R.styleable#TextView_softInputShownOnFocus + * @hide + */ + @android.view.RemotableViewMethod + public final void setSoftInputShownOnFocus(boolean show) { + mSoftInputShownOnFocus = show; + } + + /** + * Returns whether the soft input method will be made visible when this + * TextView gets focused. The default is true. + * + * @attr ref android.R.styleable#TextView_softInputShownOnFocus + * @hide + */ + public final boolean getSoftInputShownOnFocus() { + return mSoftInputShownOnFocus; + } + + /** * Returns the list of URLSpans attached to the text * (by {@link Linkify} or otherwise) if any. You can call * {@link URLSpan#getURL} on them to find where they link to @@ -2942,15 +2975,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener sp.removeSpan(cw); } - SuggestionSpan[] suggestionSpans = sp.getSpans(0, sp.length(), SuggestionSpan.class); - for (int i = 0; i < suggestionSpans.length; i++) { - int flags = suggestionSpans[i].getFlags(); - if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 - && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { - sp.removeSpan(suggestionSpans[i]); - } - } - + removeMisspelledSpans(sp); sp.removeSpan(mSuggestionRangeSpan); ss.text = sp; @@ -2970,6 +2995,18 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return superState; } + void removeMisspelledSpans(Spannable spannable) { + SuggestionSpan[] suggestionSpans = spannable.getSpans(0, spannable.length(), + SuggestionSpan.class); + for (int i = 0; i < suggestionSpans.length; i++) { + int flags = suggestionSpans[i].getFlags(); + if ((flags & SuggestionSpan.FLAG_EASY_CORRECT) != 0 + && (flags & SuggestionSpan.FLAG_MISSPELLED) != 0) { + spannable.removeSpan(suggestionSpans[i]); + } + } + } + @Override public void onRestoreInstanceState(Parcelable state) { if (!(state instanceof SavedState)) { @@ -5326,7 +5363,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // don't let it be inserted into the text. if ((event.getFlags() & KeyEvent.FLAG_EDITOR_ACTION) != 0 || shouldAdvanceFocusOnEnter()) { - if (mOnClickListener != null) { + if (hasOnClickListeners()) { return 0; } return -1; @@ -5460,12 +5497,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * call performClick(), but that won't do anything in * this case.) */ - if (mOnClickListener == null) { + if (hasOnClickListeners()) { if (mMovement != null && mText instanceof Editable && mLayout != null && onCheckIsTextEditor()) { InputMethodManager imm = InputMethodManager.peekInstance(); viewClicked(imm); - if (imm != null) { + if (imm != null && mSoftInputShownOnFocus) { imm.showSoftInput(this, 0); } } @@ -5498,7 +5535,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * call performClick(), but that won't do anything in * this case.) */ - if (mOnClickListener == null) { + if (hasOnClickListeners()) { View v = focusSearch(FOCUS_DOWN); if (v != null) { @@ -8207,6 +8244,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } hideControllers(); + if (mSuggestionsPopupWindow != null) { + mSuggestionsPopupWindow.onParentLostFocus(); + } } startStopMarquee(hasWindowFocus); @@ -8304,7 +8344,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // Show the IME, except when selecting in read-only text. final InputMethodManager imm = InputMethodManager.peekInstance(); viewClicked(imm); - if (!mTextIsSelectable) { + if (!mTextIsSelectable && mSoftInputShownOnFocus) { handled |= imm != null && imm.showSoftInput(this, 0); } @@ -8840,15 +8880,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener selectionStart = ((Spanned) mText).getSpanStart(urlSpan); selectionEnd = ((Spanned) mText).getSpanEnd(urlSpan); } else { - if (mWordIterator == null) { - mWordIterator = new WordIterator(); - } - mWordIterator.setCharSequence(mText, minOffset, maxOffset); + final WordIterator wordIterator = getWordIterator(); + wordIterator.setCharSequence(mText, minOffset, maxOffset); - selectionStart = mWordIterator.getBeginning(minOffset); + selectionStart = wordIterator.getBeginning(minOffset); if (selectionStart == BreakIterator.DONE) return false; - selectionEnd = mWordIterator.getEnd(maxOffset); + selectionEnd = wordIterator.getEnd(maxOffset); if (selectionEnd == BreakIterator.DONE) return false; if (selectionStart == selectionEnd) { @@ -8863,6 +8901,43 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return selectionEnd > selectionStart; } + /** + * This is a temporary method. Future versions may support multi-locale text. + * + * @return The current locale used in this TextView, based on the current IME's locale, + * or the system default locale if this is not defined. + * @hide + */ + public Locale getLocale() { + Locale locale = Locale.getDefault(); + final InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + final InputMethodSubtype currentInputMethodSubtype = imm.getCurrentInputMethodSubtype(); + if (currentInputMethodSubtype != null) { + String localeString = currentInputMethodSubtype.getLocale(); + if (!TextUtils.isEmpty(localeString)) { + locale = new Locale(localeString); + } + } + } + return locale; + } + + void onLocaleChanged() { + // Will be re-created on demand in getWordIterator with the proper new locale + mWordIterator = null; + } + + /** + * @hide + */ + public WordIterator getWordIterator() { + if (mWordIterator == null) { + mWordIterator = new WordIterator(getLocale()); + } + return mWordIterator; + } + private long getCharRange(int offset) { final int textLength = mText.length(); if (offset + 1 < textLength) { @@ -8984,51 +9059,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener sendAccessibilityEventUnchecked(event); } - @Override - protected void onCreateContextMenu(ContextMenu menu) { - super.onCreateContextMenu(menu); - boolean added = false; - mContextMenuTriggeredByKey = mDPadCenterIsDown || mEnterKeyIsDown; - // Problem with context menu on long press: the menu appears while the key in down and when - // the key is released, the view does not receive the key_up event. - // We need two layers of flags: mDPadCenterIsDown and mEnterKeyIsDown are set in key down/up - // events. We cannot simply clear these flags in onTextContextMenuItem since - // it may not be called (if the user/ discards the context menu with the back key). - // We clear these flags here and mContextMenuTriggeredByKey saves that state so that it is - // available in onTextContextMenuItem. - mDPadCenterIsDown = mEnterKeyIsDown = false; - - MenuHandler handler = new MenuHandler(); - - if (mText instanceof Spanned && hasSelectionController()) { - long lastTouchOffset = getLastTouchOffsets(); - final int selStart = extractRangeStartFromLong(lastTouchOffset); - final int selEnd = extractRangeEndFromLong(lastTouchOffset); - - URLSpan[] urls = ((Spanned) mText).getSpans(selStart, selEnd, URLSpan.class); - if (urls.length > 0) { - menu.add(0, ID_COPY_URL, 0, com.android.internal.R.string.copyUrl). - setOnMenuItemClickListener(handler); - - added = true; - } - } - - // The context menu is not empty, which will prevent the selection mode from starting. - // Add a entry to start it in the context menu. - // TODO Does not handle the case where a subclass does not call super.thisMethod or - // populates the menu AFTER this call. - if (menu.size() > 0) { - menu.add(0, ID_SELECTION_MODE, 0, com.android.internal.R.string.selectTextMode). - setOnMenuItemClickListener(handler); - added = true; - } - - if (added) { - menu.setHeaderTitle(com.android.internal.R.string.editTextMenuTitle); - } - } - /** * Returns whether this text view is a current input method target. The * default implementation just checks with {@link InputMethodManager}. @@ -9043,9 +9073,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private static final int ID_CUT = android.R.id.cut; private static final int ID_COPY = android.R.id.copy; private static final int ID_PASTE = android.R.id.paste; - // Context menu entries - private static final int ID_COPY_URL = android.R.id.copyUrl; - private static final int ID_SELECTION_MODE = android.R.id.selectTextMode; private class MenuHandler implements MenuItem.OnMenuItemClickListener { public boolean onMenuItemClick(MenuItem item) { @@ -9055,9 +9082,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * Called when a context menu option for the text view is selected. Currently - * this will be {@link android.R.id#copyUrl}, {@link android.R.id#selectTextMode}, - * {@link android.R.id#selectAll}, {@link android.R.id#paste}, {@link android.R.id#cut} - * or {@link android.R.id#copy}. + * this will be one of {@link android.R.id#selectAll}, {@link android.R.id#cut}, + * {@link android.R.id#copy} or {@link android.R.id#paste}. * * @return true if the context menu item action was performed. */ @@ -9074,34 +9100,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } switch (id) { - case ID_COPY_URL: - URLSpan[] urls = ((Spanned) mText).getSpans(min, max, URLSpan.class); - if (urls.length >= 1) { - ClipData clip = null; - for (int i=0; i<urls.length; i++) { - Uri uri = Uri.parse(urls[0].getURL()); - if (clip == null) { - clip = ClipData.newRawUri(null, uri); - } else { - clip.addItem(new ClipData.Item(uri)); - } - } - if (clip != null) { - setPrimaryClip(clip); - } - } - stopSelectionActionMode(); - return true; - - case ID_SELECTION_MODE: - if (mSelectionActionMode != null) { - // Selection mode is already started, simply change selected part. - selectCurrentWord(); - } else { - startSelectionActionMode(); - } - return true; - case ID_SELECT_ALL: // This does not enter text selection mode. Text is highlighted, so that it can be // bulk edited, like selectAllOnFocus does. Returns true even if text is empty. @@ -9548,6 +9546,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private SuggestionInfo[] mSuggestionInfos; private int mNumberOfSuggestions; private boolean mCursorWasVisibleBeforeSuggestions; + private boolean mIsShowingUp = false; private SuggestionAdapter mSuggestionsAdapter; private final Comparator<SuggestionSpan> mSuggestionSpanComparator; private final HashMap<SuggestionSpan, Integer> mSpansLengths; @@ -9603,6 +9602,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } + public boolean isShowingUp() { + return mIsShowingUp; + } + + public void onParentLostFocus() { + mIsShowingUp = false; + } + private class SuggestionInfo { int suggestionStart, suggestionEnd; // range of actual suggestion within text SuggestionSpan suggestionSpan; // the SuggestionSpan that this TextView represents @@ -9610,15 +9617,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener SpannableStringBuilder text = new SpannableStringBuilder(); TextAppearanceSpan highlightSpan = new TextAppearanceSpan(mContext, android.R.style.TextAppearance_SuggestionHighlight); - - void removeMisspelledFlag() { - int suggestionSpanFlags = suggestionSpan.getFlags(); - if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) { - suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED; - suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT; - suggestionSpan.setFlags(suggestionSpanFlags); - } - } } private class SuggestionAdapter extends BaseAdapter { @@ -9715,6 +9713,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener updateSuggestions(); mCursorWasVisibleBeforeSuggestions = mCursorVisible; setCursorVisible(false); + mIsShowingUp = true; super.show(); } @@ -9936,6 +9935,14 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener suggestionSpansStarts[i] = editable.getSpanStart(suggestionSpan); suggestionSpansEnds[i] = editable.getSpanEnd(suggestionSpan); suggestionSpansFlags[i] = editable.getSpanFlags(suggestionSpan); + + // Remove potential misspelled flags + int suggestionSpanFlags = suggestionSpan.getFlags(); + if ((suggestionSpanFlags & SuggestionSpan.FLAG_MISSPELLED) > 0) { + suggestionSpanFlags &= ~SuggestionSpan.FLAG_MISSPELLED; + suggestionSpanFlags &= ~SuggestionSpan.FLAG_EASY_CORRECT; + suggestionSpan.setFlags(suggestionSpanFlags); + } } final int suggestionStart = suggestionInfo.suggestionStart; @@ -9944,8 +9951,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener suggestionStart, suggestionEnd).toString(); editable.replace(spanStart, spanEnd, suggestion); - suggestionInfo.removeMisspelledFlag(); - // Notify source IME of the suggestion pick. Do this before swaping texts. if (!TextUtils.isEmpty( suggestionInfo.suggestionSpan.getNotificationTargetClassName())) { @@ -10121,7 +10126,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final boolean selectionStarted = mSelectionActionMode != null || extractedTextModeWillBeStartedFullScreen; - if (selectionStarted && !mTextIsSelectable && imm != null) { + if (selectionStarted && !mTextIsSelectable && imm != null && mSoftInputShownOnFocus) { // Show the IME to be able to replace text, except when selecting non editable text. imm.showSoftInput(this, 0, null); } @@ -11121,6 +11126,10 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private void hideCursorControllers() { + if (mSuggestionsPopupWindow != null && !mSuggestionsPopupWindow.isShowingUp()) { + // Should be done before hide insertion point controller since it triggers a show of it + mSuggestionsPopupWindow.hide(); + } hideInsertionPointCursorController(); stopSelectionActionMode(); } diff --git a/core/java/android/widget/ZoomButton.java b/core/java/android/widget/ZoomButton.java index c5fa18c..eb372ca 100644 --- a/core/java/android/widget/ZoomButton.java +++ b/core/java/android/widget/ZoomButton.java @@ -29,8 +29,8 @@ public class ZoomButton extends ImageButton implements OnLongClickListener { private final Handler mHandler; private final Runnable mRunnable = new Runnable() { public void run() { - if ((mOnClickListener != null) && mIsInLongpress && isEnabled()) { - mOnClickListener.onClick(ZoomButton.this); + if (hasOnClickListeners() && mIsInLongpress && isEnabled()) { + callOnClick(); mHandler.postDelayed(this, mZoomSpeed); } } diff --git a/core/java/com/android/internal/net/NetworkStatsFactory.java b/core/java/com/android/internal/net/NetworkStatsFactory.java index ee3f23b..41993c4 100644 --- a/core/java/com/android/internal/net/NetworkStatsFactory.java +++ b/core/java/com/android/internal/net/NetworkStatsFactory.java @@ -25,12 +25,14 @@ import android.net.NetworkStats; import android.os.SystemClock; import android.util.Slog; +import com.android.internal.util.ProcFileReader; import com.google.android.collect.Lists; import com.google.android.collect.Maps; import com.google.android.collect.Sets; import java.io.BufferedReader; import java.io.File; +import java.io.FileInputStream; import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; @@ -107,6 +109,7 @@ public class NetworkStatsFactory { final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 6); final NetworkStats.Entry entry = new NetworkStats.Entry(); + // TODO: transition to ProcFileReader // TODO: read directly from proc once headers are added final ArrayList<String> keys = Lists.newArrayList(KEY_IFACE, KEY_ACTIVE, KEY_SNAP_RX_BYTES, KEY_SNAP_RX_PACKETS, KEY_SNAP_TX_BYTES, KEY_SNAP_TX_PACKETS, KEY_RX_BYTES, @@ -257,71 +260,58 @@ public class NetworkStatsFactory { final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 24); final NetworkStats.Entry entry = new NetworkStats.Entry(); - // TODO: remove knownLines check once 5087722 verified - final HashSet<String> knownLines = Sets.newHashSet(); - // TODO: remove lastIdx check once 5270106 verified - int lastIdx; + int idx = 1; + int lastIdx = 1; - final ArrayList<String> keys = Lists.newArrayList(); - final ArrayList<String> values = Lists.newArrayList(); - final HashMap<String, String> parsed = Maps.newHashMap(); - - BufferedReader reader = null; - String line = null; + ProcFileReader reader = null; try { - reader = new BufferedReader(new FileReader(mStatsXtUid)); - - // parse first line as header - line = reader.readLine(); - splitLine(line, keys); - lastIdx = 1; - - // parse remaining lines - while ((line = reader.readLine()) != null) { - splitLine(line, values); - parseLine(keys, values, parsed); + // open and consume header line + reader = new ProcFileReader(new FileInputStream(mStatsXtUid)); + reader.finishLine(); - if (!knownLines.add(line)) { - throw new IllegalStateException("duplicate proc entry: " + line); - } - - final int idx = getParsedInt(parsed, KEY_IDX); + while (reader.hasMoreData()) { + idx = reader.nextInt(); if (idx != lastIdx + 1) { throw new IllegalStateException( "inconsistent idx=" + idx + " after lastIdx=" + lastIdx); } lastIdx = idx; - entry.iface = parsed.get(KEY_IFACE); - entry.uid = getParsedInt(parsed, KEY_UID); - entry.set = getParsedInt(parsed, KEY_COUNTER_SET); - entry.tag = kernelToTag(parsed.get(KEY_TAG_HEX)); - entry.rxBytes = getParsedLong(parsed, KEY_RX_BYTES); - entry.rxPackets = getParsedLong(parsed, KEY_RX_PACKETS); - entry.txBytes = getParsedLong(parsed, KEY_TX_BYTES); - entry.txPackets = getParsedLong(parsed, KEY_TX_PACKETS); + entry.iface = reader.nextString(); + entry.tag = kernelToTag(reader.nextString()); + entry.uid = reader.nextInt(); + entry.set = reader.nextInt(); + entry.rxBytes = reader.nextLong(); + entry.rxPackets = reader.nextLong(); + entry.txBytes = reader.nextLong(); + entry.txPackets = reader.nextLong(); if (limitUid == UID_ALL || limitUid == entry.uid) { stats.addValues(entry); } + + reader.finishLine(); } } catch (NullPointerException e) { - throw new IllegalStateException("problem parsing line: " + line, e); + throw new IllegalStateException("problem parsing idx " + idx, e); } catch (NumberFormatException e) { - throw new IllegalStateException("problem parsing line: " + line, e); + throw new IllegalStateException("problem parsing idx " + idx, e); } catch (IOException e) { - throw new IllegalStateException("problem parsing line: " + line, e); + throw new IllegalStateException("problem parsing idx " + idx, e); } finally { IoUtils.closeQuietly(reader); } + return stats; } + @Deprecated private static int getParsedInt(HashMap<String, String> parsed, String key) { final String value = parsed.get(key); return value != null ? Integer.parseInt(value) : 0; } + @Deprecated private static long getParsedLong(HashMap<String, String> parsed, String key) { final String value = parsed.get(key); return value != null ? Long.parseLong(value) : 0; @@ -330,6 +320,7 @@ public class NetworkStatsFactory { /** * Split given line into {@link ArrayList}. */ + @Deprecated private static void splitLine(String line, ArrayList<String> outSplit) { outSplit.clear(); @@ -343,6 +334,7 @@ public class NetworkStatsFactory { * Zip the two given {@link ArrayList} as key and value pairs into * {@link HashMap}. */ + @Deprecated private static void parseLine( ArrayList<String> keys, ArrayList<String> values, HashMap<String, String> outParsed) { outParsed.clear(); diff --git a/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl b/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl index 5a00603..3c61968 100644 --- a/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl +++ b/core/java/com/android/internal/textservice/ISpellCheckerSession.aidl @@ -25,4 +25,5 @@ oneway interface ISpellCheckerSession { void onGetSuggestionsMultiple( in TextInfo[] textInfos, int suggestionsLimit, boolean multipleWords); void onCancel(); + void onClose(); } diff --git a/core/java/com/android/internal/util/ProcFileReader.java b/core/java/com/android/internal/util/ProcFileReader.java new file mode 100644 index 0000000..72e1f0f --- /dev/null +++ b/core/java/com/android/internal/util/ProcFileReader.java @@ -0,0 +1,199 @@ +/* + * Copyright (C) 2011 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 com.android.internal.util; + +import java.io.Closeable; +import java.io.IOException; +import java.io.InputStream; +import java.nio.charset.Charsets; + +/** + * Reader that specializes in parsing {@code /proc/} files quickly. Walks + * through the stream using a single space {@code ' '} as token separator, and + * requires each line boundary to be explicitly acknowledged using + * {@link #finishLine()}. Assumes {@link Charsets#US_ASCII} encoding. + * <p> + * Currently doesn't support formats based on {@code \0}, tabs, or repeated + * delimiters. + */ +public class ProcFileReader implements Closeable { + private final InputStream mStream; + private final byte[] mBuffer; + + /** Write pointer in {@link #mBuffer}. */ + private int mTail; + /** Flag when last read token finished current line. */ + private boolean mLineFinished; + + public ProcFileReader(InputStream stream) throws IOException { + this(stream, 4096); + } + + public ProcFileReader(InputStream stream, int bufferSize) throws IOException { + mStream = stream; + mBuffer = new byte[bufferSize]; + + // read enough to answer hasMoreData + fillBuf(); + } + + /** + * Read more data from {@link #mStream} into internal buffer. + */ + private int fillBuf() throws IOException { + final int length = mBuffer.length - mTail; + if (length == 0) { + throw new IOException("attempting to fill already-full buffer"); + } + + final int read = mStream.read(mBuffer, mTail, length); + if (read != -1) { + mTail += read; + } + return read; + } + + /** + * Consume number of bytes from beginning of internal buffer. If consuming + * all remaining bytes, will attempt to {@link #fillBuf()}. + */ + private void consumeBuf(int count) throws IOException { + // TODO: consider moving to read pointer, but for now traceview says + // these copies aren't a bottleneck. + System.arraycopy(mBuffer, count, mBuffer, 0, mTail - count); + mTail -= count; + if (mTail == 0) { + fillBuf(); + } + } + + /** + * Find buffer index of next token delimiter, usually space or newline. Will + * fill buffer as needed. + */ + private int nextTokenIndex() throws IOException { + if (mLineFinished) { + throw new IOException("no tokens remaining on current line"); + } + + int i = 0; + do { + // scan forward for token boundary + for (; i < mTail; i++) { + final byte b = mBuffer[i]; + if (b == '\n') { + mLineFinished = true; + return i; + } + if (b == ' ') { + return i; + } + } + } while (fillBuf() > 0); + + throw new IOException("end of stream while looking for token boundary"); + } + + /** + * Check if stream has more data to be parsed. + */ + public boolean hasMoreData() { + return mTail > 0; + } + + /** + * Finish current line, skipping any remaining data. + */ + public void finishLine() throws IOException { + // last token already finished line; reset silently + if (mLineFinished) { + mLineFinished = false; + return; + } + + int i = 0; + do { + // scan forward for line boundary and consume + for (; i < mTail; i++) { + if (mBuffer[i] == '\n') { + consumeBuf(i + 1); + return; + } + } + } while (fillBuf() > 0); + + throw new IOException("end of stream while looking for line boundary"); + } + + /** + * Parse and return next token as {@link String}. + */ + public String nextString() throws IOException { + final int tokenIndex = nextTokenIndex(); + final String s = new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII); + consumeBuf(tokenIndex + 1); + return s; + } + + /** + * Parse and return next token as base-10 encoded {@code long}. + */ + public long nextLong() throws IOException { + final int tokenIndex = nextTokenIndex(); + final boolean negative = mBuffer[0] == '-'; + + // TODO: refactor into something like IntegralToString + long result = 0; + for (int i = negative ? 1 : 0; i < tokenIndex; i++) { + final int digit = mBuffer[i] - '0'; + if (digit < 0 || digit > 9) { + throw invalidLong(tokenIndex); + } + + // always parse as negative number and apply sign later; this + // correctly handles MIN_VALUE which is "larger" than MAX_VALUE. + final long next = result * 10 - digit; + if (next > result) { + throw invalidLong(tokenIndex); + } + result = next; + } + + consumeBuf(tokenIndex + 1); + return negative ? result : -result; + } + + private NumberFormatException invalidLong(int tokenIndex) { + return new NumberFormatException( + "invalid long: " + new String(mBuffer, 0, tokenIndex, Charsets.US_ASCII)); + } + + /** + * Parse and return next token as base-10 encoded {@code int}. + */ + public int nextInt() throws IOException { + final long value = nextLong(); + if (value > Integer.MAX_VALUE || value < Integer.MIN_VALUE) { + throw new NumberFormatException("parsed value larger than integer"); + } + return (int) value; + } + + public void close() throws IOException { + mStream.close(); + } +} diff --git a/core/java/com/android/internal/view/menu/ActionMenuItemView.java b/core/java/com/android/internal/view/menu/ActionMenuItemView.java index e245960..a10d241 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ActionMenuItemView.java @@ -18,6 +18,7 @@ package com.android.internal.view.menu; import android.content.Context; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Rect; import android.graphics.drawable.Drawable; import android.text.TextUtils; @@ -46,8 +47,8 @@ public class ActionMenuItemView extends LinearLayout private ImageButton mImageButton; private Button mTextButton; private boolean mAllowTextWithIcon; - private boolean mShowTextAllCaps; private boolean mExpandedFormat; + private int mMinWidth; public ActionMenuItemView(Context context) { this(context, null); @@ -62,7 +63,11 @@ public class ActionMenuItemView extends LinearLayout final Resources res = context.getResources(); mAllowTextWithIcon = res.getBoolean( com.android.internal.R.bool.config_allowActionMenuItemTextWithIcon); - mShowTextAllCaps = res.getBoolean(com.android.internal.R.bool.config_actionMenuItemAllCaps); + TypedArray a = context.obtainStyledAttributes(attrs, + com.android.internal.R.styleable.ActionMenuItemView, 0, 0); + mMinWidth = a.getDimensionPixelSize( + com.android.internal.R.styleable.ActionMenuItemView_minWidth, 0); + a.recycle(); } @Override @@ -228,4 +233,21 @@ public class ActionMenuItemView extends LinearLayout cheatSheet.show(); return true; } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + super.onMeasure(widthMeasureSpec, heightMeasureSpec); + + final int widthMode = MeasureSpec.getMode(widthMeasureSpec); + final int specSize = MeasureSpec.getSize(widthMeasureSpec); + final int oldMeasuredWidth = getMeasuredWidth(); + final int targetWidth = widthMode == MeasureSpec.AT_MOST ? Math.min(specSize, mMinWidth) + : mMinWidth; + + if (widthMode != MeasureSpec.EXACTLY && mMinWidth > 0 && oldMeasuredWidth < targetWidth) { + // Remeasure at exactly the minimum width. + super.onMeasure(MeasureSpec.makeMeasureSpec(targetWidth, MeasureSpec.EXACTLY), + heightMeasureSpec); + } + } } diff --git a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java index f25d65f..530809b 100644 --- a/core/java/com/android/internal/view/menu/ActionMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/ActionMenuPresenter.java @@ -300,6 +300,7 @@ public class ActionMenuPresenter extends BaseMenuPresenter public boolean hideOverflowMenu() { if (mPostedOpenRunnable != null && mMenuView != null) { ((View) mMenuView).removeCallbacks(mPostedOpenRunnable); + mPostedOpenRunnable = null; return true; } @@ -653,10 +654,11 @@ public class ActionMenuPresenter extends BaseMenuPresenter public void run() { mMenu.changeMenuMode(); - if (mPopup.tryShow()) { + final View menuView = (View) mMenuView; + if (menuView != null && menuView.getWindowToken() != null && mPopup.tryShow()) { mOverflowPopup = mPopup; - mPostedOpenRunnable = null; } + mPostedOpenRunnable = null; } } } diff --git a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java index 1e06b5a..db0d6dd 100644 --- a/core/java/com/android/internal/view/menu/BaseMenuPresenter.java +++ b/core/java/com/android/internal/view/menu/BaseMenuPresenter.java @@ -91,7 +91,14 @@ public abstract class BaseMenuPresenter implements MenuPresenter { MenuItemImpl item = visibleItems.get(i); if (shouldIncludeItem(childIndex, item)) { final View convertView = parent.getChildAt(childIndex); + final MenuItemImpl oldItem = convertView instanceof MenuView.ItemView ? + ((MenuView.ItemView) convertView).getItemData() : null; final View itemView = getItemView(item, convertView, parent); + if (item != oldItem) { + // Don't let old states linger with new data. + itemView.setPressed(false); + itemView.jumpDrawablesToCurrentState(); + } if (itemView != convertView) { addItemView(itemView, childIndex); } diff --git a/core/java/com/android/internal/view/menu/ExpandedMenuView.java b/core/java/com/android/internal/view/menu/ExpandedMenuView.java index 723ece4..47058ad 100644 --- a/core/java/com/android/internal/view/menu/ExpandedMenuView.java +++ b/core/java/com/android/internal/view/menu/ExpandedMenuView.java @@ -63,11 +63,6 @@ public final class ExpandedMenuView extends ListView implements ItemInvoker, Men setChildrenDrawingCacheEnabled(false); } - @Override - protected boolean recycleOnMeasure() { - return false; - } - public boolean invokeItem(MenuItemImpl item) { return mMenu.performItemAction(item, 0); } diff --git a/core/java/com/android/internal/view/menu/ListMenuItemView.java b/core/java/com/android/internal/view/menu/ListMenuItemView.java index a1e16d4..df579c6 100644 --- a/core/java/com/android/internal/view/menu/ListMenuItemView.java +++ b/core/java/com/android/internal/view/menu/ListMenuItemView.java @@ -34,6 +34,7 @@ import android.widget.TextView; * The item view for each item in the ListView-based MenuViews. */ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView { + private static final String TAG = "ListMenuItemView"; private MenuItemImpl mItemData; private ImageView mIconView; @@ -121,27 +122,25 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView } public void setCheckable(boolean checkable) { - if (!checkable && mRadioButton == null && mCheckBox == null) { return; } - if (mRadioButton == null) { - insertRadioButton(); - } - if (mCheckBox == null) { - insertCheckBox(); - } - // Depending on whether its exclusive check or not, the checkbox or // radio button will be the one in use (and the other will be otherCompoundButton) final CompoundButton compoundButton; final CompoundButton otherCompoundButton; if (mItemData.isExclusiveCheckable()) { + if (mRadioButton == null) { + insertRadioButton(); + } compoundButton = mRadioButton; otherCompoundButton = mCheckBox; } else { + if (mCheckBox == null) { + insertCheckBox(); + } compoundButton = mCheckBox; otherCompoundButton = mRadioButton; } @@ -155,12 +154,12 @@ public class ListMenuItemView extends LinearLayout implements MenuView.ItemView } // Make sure the other compound button isn't visible - if (otherCompoundButton.getVisibility() != GONE) { + if (otherCompoundButton != null && otherCompoundButton.getVisibility() != GONE) { otherCompoundButton.setVisibility(GONE); } } else { - mCheckBox.setVisibility(GONE); - mRadioButton.setVisibility(GONE); + if (mCheckBox != null) mCheckBox.setVisibility(GONE); + if (mRadioButton != null) mRadioButton.setVisibility(GONE); } } diff --git a/core/java/com/android/internal/widget/ActionBarContextView.java b/core/java/com/android/internal/widget/ActionBarContextView.java index 18d45f7..ed02636 100644 --- a/core/java/com/android/internal/widget/ActionBarContextView.java +++ b/core/java/com/android/internal/widget/ActionBarContextView.java @@ -216,6 +216,9 @@ public class ActionBarContextView extends AbsActionBarView implements AnimatorLi }); final MenuBuilder menu = (MenuBuilder) mode.getMenu(); + if (mActionMenuPresenter != null) { + mActionMenuPresenter.dismissPopupMenus(); + } mActionMenuPresenter = new ActionMenuPresenter(mContext); mActionMenuPresenter.setReserveOverflow(true); diff --git a/core/java/com/android/internal/widget/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index e131242..4714be8 100644 --- a/core/java/com/android/internal/widget/ActionBarView.java +++ b/core/java/com/android/internal/widget/ActionBarView.java @@ -518,6 +518,7 @@ public class ActionBarView extends AbsActionBarView { public void setHomeButtonEnabled(boolean enable) { mHomeLayout.setEnabled(enable); + mHomeLayout.setFocusable(enable); // Make sure the home button has an accurate content description for accessibility. if (!enable) { mHomeLayout.setContentDescription(null); diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java index 18a4794..6f24eba 100644 --- a/core/java/com/android/internal/widget/DigitalClock.java +++ b/core/java/com/android/internal/widget/DigitalClock.java @@ -168,6 +168,8 @@ public class DigitalClock extends RelativeLayout { /* The time display consists of two tones. That's why we have two overlapping text views. */ mTimeDisplayBackground = (TextView) findViewById(R.id.timeDisplayBackground); mTimeDisplayBackground.setTypeface(sBackgroundFont); + mTimeDisplayBackground.setVisibility(View.INVISIBLE); + mTimeDisplayForeground = (TextView) findViewById(R.id.timeDisplayForeground); mTimeDisplayForeground.setTypeface(sForegroundFont); mAmPm = new AmPm(this, null); diff --git a/core/java/com/android/internal/widget/LockPatternUtils.java b/core/java/com/android/internal/widget/LockPatternUtils.java index d5450e4..17b8acf 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -439,17 +439,6 @@ public class LockPatternUtils { } /** - * Calls back SetupFaceLock to save the temporary gallery file if this is the backup lock. - * This doesn't have to verify that biometric is enabled because it's only called in that case - */ - void moveTempGallery() { - Intent intent = new Intent().setClassName("com.android.facelock", - "com.android.facelock.SetupFaceLock"); - intent.putExtra("moveTempGallery", true); - mContext.startActivity(intent); - } - - /** * Calls back SetupFaceLock to delete the temporary gallery file */ public void deleteTempGallery() { @@ -501,8 +490,7 @@ public class LockPatternUtils { setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK); setLong(PASSWORD_TYPE_ALTERNATE_KEY, DevicePolicyManager.PASSWORD_QUALITY_SOMETHING); - setBoolean(BIOMETRIC_WEAK_EVER_CHOSEN_KEY, true); - moveTempGallery(); + finishBiometricWeak(); } dpm.setActivePasswordState(DevicePolicyManager.PASSWORD_QUALITY_SOMETHING, pattern .size(), 0, 0, 0, 0, 0, 0); @@ -619,8 +607,7 @@ public class LockPatternUtils { } else { setLong(PASSWORD_TYPE_KEY, DevicePolicyManager.PASSWORD_QUALITY_BIOMETRIC_WEAK); setLong(PASSWORD_TYPE_ALTERNATE_KEY, Math.max(quality, computedQuality)); - setBoolean(BIOMETRIC_WEAK_EVER_CHOSEN_KEY, true); - moveTempGallery(); + finishBiometricWeak(); } if (computedQuality != DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED) { int letters = 0; @@ -1087,4 +1074,16 @@ public class LockPatternUtils { } return false; } + + private void finishBiometricWeak() { + setBoolean(BIOMETRIC_WEAK_EVER_CHOSEN_KEY, true); + + // Launch intent to show final screen, this also + // moves the temporary gallery to the actual gallery + Intent intent = new Intent(); + intent.setClassName("com.android.facelock", + "com.android.facelock.SetupEndScreen"); + mContext.startActivity(intent); + } + } diff --git a/core/java/com/android/internal/widget/WaveView.java b/core/java/com/android/internal/widget/WaveView.java index 288865f..2d89234 100644 --- a/core/java/com/android/internal/widget/WaveView.java +++ b/core/java/com/android/internal/widget/WaveView.java @@ -26,10 +26,13 @@ import android.graphics.BitmapFactory; import android.graphics.Canvas; import android.graphics.drawable.BitmapDrawable; import android.os.Vibrator; +import android.text.TextUtils; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; +import android.view.accessibility.AccessibilityEvent; +import android.view.accessibility.AccessibilityManager; import com.android.internal.R; @@ -64,6 +67,18 @@ public class WaveView extends View implements ValueAnimator.AnimatorUpdateListen private static final long DELAY_INCREMENT2 = 12; // increment per wave while not tracking private static final long WAVE_DELAY = WAVE_DURATION / WAVE_COUNT; // initial propagation delay + /** + * The scale by which to multiply the unlock handle width to compute the radius + * in which it can be grabbed when accessibility is disabled. + */ + private static final float GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLED = 0.5f; + + /** + * The scale by which to multiply the unlock handle width to compute the radius + * in which it can be grabbed when accessibility is enabled (more generous). + */ + private static final float GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_ENABLED = 1.0f; + private Vibrator mVibrator; private OnTriggerListener mOnTriggerListener; private ArrayList<DrawableHolder> mDrawables = new ArrayList<DrawableHolder>(3); @@ -451,6 +466,27 @@ public class WaveView extends View implements ValueAnimator.AnimatorUpdateListen }; @Override + public boolean onHoverEvent(MotionEvent event) { + if (AccessibilityManager.getInstance(mContext).isTouchExplorationEnabled()) { + final int action = event.getAction(); + switch (action) { + case MotionEvent.ACTION_HOVER_ENTER: + event.setAction(MotionEvent.ACTION_DOWN); + break; + case MotionEvent.ACTION_HOVER_MOVE: + event.setAction(MotionEvent.ACTION_MOVE); + break; + case MotionEvent.ACTION_HOVER_EXIT: + event.setAction(MotionEvent.ACTION_UP); + break; + } + onTouchEvent(event); + event.setAction(action); + } + return super.onHoverEvent(event); + } + + @Override public boolean onTouchEvent(MotionEvent event) { final int action = event.getAction(); mMouseX = event.getX(); @@ -460,21 +496,12 @@ public class WaveView extends View implements ValueAnimator.AnimatorUpdateListen case MotionEvent.ACTION_DOWN: removeCallbacks(mLockTimerActions); mFingerDown = true; - setGrabbedState(OnTriggerListener.CENTER_HANDLE); - { - float x = mMouseX - mUnlockHalo.getX(); - float y = mMouseY - mUnlockHalo.getY(); - float dist = (float) Math.hypot(x, y); - if (dist < mUnlockHalo.getWidth()*0.5f) { - if (mLockState == STATE_READY) { - mLockState = STATE_START_ATTEMPT; - } - } - } + tryTransitionToStartAttemptState(event); handled = true; break; case MotionEvent.ACTION_MOVE: + tryTransitionToStartAttemptState(event); handled = true; break; @@ -502,6 +529,47 @@ public class WaveView extends View implements ValueAnimator.AnimatorUpdateListen } /** + * Tries to transition to start attempt state. + * + * @param event A motion event. + */ + private void tryTransitionToStartAttemptState(MotionEvent event) { + final float dx = event.getX() - mUnlockHalo.getX(); + final float dy = event.getY() - mUnlockHalo.getY(); + float dist = (float) Math.hypot(dx, dy); + if (dist <= getScaledGrabHandleRadius()) { + setGrabbedState(OnTriggerListener.CENTER_HANDLE); + if (mLockState == STATE_READY) { + mLockState = STATE_START_ATTEMPT; + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + announceUnlockHandle(); + } + } + } + } + + /** + * @return The radius in which the handle is grabbed scaled based on + * whether accessibility is enabled. + */ + private float getScaledGrabHandleRadius() { + if (AccessibilityManager.getInstance(mContext).isEnabled()) { + return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_ENABLED * mUnlockHalo.getWidth(); + } else { + return GRAB_HANDLE_RADIUS_SCALE_ACCESSIBILITY_DISABLED * mUnlockHalo.getWidth(); + } + } + + /** + * Announces the unlock handle if accessibility is enabled. + */ + private void announceUnlockHandle() { + setContentDescription(mContext.getString(R.string.description_target_unlock_tablet)); + sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_FOCUSED); + setContentDescription(null); + } + + /** * Triggers haptic feedback. */ private synchronized void vibrate(long duration) { |
