diff options
Diffstat (limited to 'core/java')
126 files changed, 4036 insertions, 1496 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..303f81b 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -45,6 +45,7 @@ import android.graphics.Canvas; import android.net.IConnectivityManager; import android.net.Proxy; import android.net.ProxyProperties; +import android.opengl.GLUtils; import android.os.AsyncTask; import android.os.Bundle; import android.os.Debug; @@ -2733,8 +2734,9 @@ public final class ActivityThread { CharSequence description; } - private class ProviderRefCount { + private static final class ProviderRefCount { public int count; + ProviderRefCount(int pCount) { count = pCount; } @@ -2769,7 +2771,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)) { @@ -3712,6 +3716,24 @@ public final class ActivityThread { } } + private void setupGraphicsSupport(LoadedApk info) { + try { + int uid = Process.myUid(); + String[] packages = getPackageManager().getPackagesForUid(uid); + + // If there are several packages in this application we won't + // initialize the graphics disk caches + if (packages.length == 1) { + ContextImpl appContext = new ContextImpl(); + appContext.init(info, null, this); + + HardwareRenderer.setupDiskCache(appContext.getCacheDir()); + } + } catch (RemoteException e) { + // Ignore + } + } + private void handleBindApplication(AppBindData data) { mBoundApplication = data; mConfiguration = new Configuration(data.config); @@ -3735,7 +3757,7 @@ public final class ActivityThread { HardwareRenderer.disable(false); } } - + if (mProfiler.profileFd != null) { mProfiler.startProfiling(); } @@ -3771,6 +3793,8 @@ public final class ActivityThread { data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); + setupGraphicsSupport(data.info); + /** * For system applications on userdebug/eng builds, log stack * traces of disk and network access to dropbox for analysis. @@ -3830,11 +3854,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(); @@ -3960,16 +3989,14 @@ public final class ActivityThread { buf.append(": "); buf.append(cpi.name); Log.i(TAG, buf.toString()); - IContentProvider cp = installProvider(context, null, cpi, false); + IContentProvider cp = installProvider(context, null, cpi, + false /*noisy*/, true /*noReleaseNeeded*/); if (cp != null) { IActivityManager.ContentProviderHolder cph = - new IActivityManager.ContentProviderHolder(cpi); + new IActivityManager.ContentProviderHolder(cpi); cph.provider = cp; + cph.noReleaseNeeded = true; results.add(cph); - // Don't ever unload this provider from the process. - synchronized(mProviderMap) { - mProviderRefCountMap.put(cp.asBinder(), new ProviderRefCount(10000)); - } } } @@ -3980,26 +4007,22 @@ public final class ActivityThread { } } - private IContentProvider getExistingProvider(Context context, String name) { - synchronized(mProviderMap) { - final ProviderClientRecord pr = mProviderMap.get(name); - if (pr != null) { - return pr.mProvider; - } - return null; - } - } - - private IContentProvider getProvider(Context context, String name) { - IContentProvider existing = getExistingProvider(context, name); - if (existing != null) { - return existing; + public final IContentProvider acquireProvider(Context c, String name) { + IContentProvider provider = acquireExistingProvider(c, name); + if (provider != null) { + return provider; } + // There is a possible race here. Another thread may try to acquire + // the same provider at the same time. When this happens, we want to ensure + // that the first one wins. + // Note that we cannot hold the lock while acquiring and installing the + // provider since it might take a long time to run and it could also potentially + // be re-entrant in the case where the provider is in the same process. IActivityManager.ContentProviderHolder holder = null; try { holder = ActivityManagerNative.getDefault().getContentProvider( - getApplicationThread(), name); + getApplicationThread(), name); } catch (RemoteException ex) { } if (holder == null) { @@ -4007,135 +4030,136 @@ public final class ActivityThread { return null; } - IContentProvider prov = installProvider(context, holder.provider, - holder.info, true); - //Slog.i(TAG, "noReleaseNeeded=" + holder.noReleaseNeeded); - if (holder.noReleaseNeeded || holder.provider == null) { - // We are not going to release the provider if it is an external - // provider that doesn't care about being released, or if it is - // a local provider running in this process. - //Slog.i(TAG, "*** NO RELEASE NEEDED"); - synchronized(mProviderMap) { - mProviderRefCountMap.put(prov.asBinder(), new ProviderRefCount(10000)); + // Install provider will increment the reference count for us, and break + // any ties in the race. + provider = installProvider(c, holder.provider, holder.info, + true /*noisy*/, holder.noReleaseNeeded); + if (holder.provider != null && provider != holder.provider) { + if (localLOGV) { + Slog.v(TAG, "acquireProvider: lost the race, releasing extraneous " + + "reference to the content provider"); + } + try { + ActivityManagerNative.getDefault().removeContentProvider( + getApplicationThread(), name); + } catch (RemoteException ex) { } } - return prov; - } - - public final IContentProvider acquireProvider(Context c, String name) { - IContentProvider provider = getProvider(c, name); - if(provider == null) - return null; - IBinder jBinder = provider.asBinder(); - synchronized(mProviderMap) { - ProviderRefCount prc = mProviderRefCountMap.get(jBinder); - if(prc == null) { - mProviderRefCountMap.put(jBinder, new ProviderRefCount(1)); - } else { - prc.count++; - } //end else - } //end synchronized return provider; } public final IContentProvider acquireExistingProvider(Context c, String name) { - IContentProvider provider = getExistingProvider(c, name); - if(provider == null) - return null; - IBinder jBinder = provider.asBinder(); - synchronized(mProviderMap) { + synchronized (mProviderMap) { + ProviderClientRecord pr = mProviderMap.get(name); + if (pr == null) { + return null; + } + + IContentProvider provider = pr.mProvider; + IBinder jBinder = provider.asBinder(); + + // Only increment the ref count if we have one. If we don't then the + // provider is not reference counted and never needs to be released. ProviderRefCount prc = mProviderRefCountMap.get(jBinder); - if(prc == null) { - mProviderRefCountMap.put(jBinder, new ProviderRefCount(1)); - } else { - prc.count++; - } //end else - } //end synchronized - return provider; + if (prc != null) { + prc.count += 1; + if (prc.count == 1) { + if (localLOGV) { + Slog.v(TAG, "acquireExistingProvider: " + + "snatched provider from the jaws of death"); + } + // Because the provider previously had a reference count of zero, + // it was scheduled to be removed. Cancel that. + mH.removeMessages(H.REMOVE_PROVIDER, provider); + } + } + return provider; + } } public final boolean releaseProvider(IContentProvider provider) { if(provider == null) { return false; } + IBinder jBinder = provider.asBinder(); - synchronized(mProviderMap) { + synchronized (mProviderMap) { ProviderRefCount prc = mProviderRefCountMap.get(jBinder); - if(prc == null) { - if(localLOGV) Slog.v(TAG, "releaseProvider::Weird shouldn't be here"); + if (prc == null) { + // The provider has no ref count, no release is needed. return false; - } else { - prc.count--; - if(prc.count == 0) { - // Schedule the actual remove asynchronously, since we - // don't know the context this will be called in. - // TODO: it would be nice to post a delayed message, so - // if we come back and need the same provider quickly - // we will still have it available. - Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, provider); - mH.sendMessage(msg); - } //end if - } //end else - } //end synchronized - return true; + } + + if (prc.count == 0) { + if (localLOGV) Slog.v(TAG, "releaseProvider: ref count already 0, how?"); + return false; + } + + prc.count -= 1; + if (prc.count == 0) { + // Schedule the actual remove asynchronously, since we don't know the context + // this will be called in. + // TODO: it would be nice to post a delayed message, so + // if we come back and need the same provider quickly + // we will still have it available. + Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, provider); + mH.sendMessage(msg); + } + return true; + } } final void completeRemoveProvider(IContentProvider provider) { IBinder jBinder = provider.asBinder(); - String name = null; + String remoteProviderName = null; synchronized(mProviderMap) { ProviderRefCount prc = mProviderRefCountMap.get(jBinder); - if(prc != null && prc.count == 0) { - mProviderRefCountMap.remove(jBinder); - //invoke removeProvider to dereference provider - name = removeProviderLocked(provider); + if (prc == null) { + // Either no release is needed (so we shouldn't be here) or the + // provider was already released. + if (localLOGV) Slog.v(TAG, "completeRemoveProvider: release not needed"); + return; + } + + if (prc.count != 0) { + // There was a race! Some other client managed to acquire + // the provider before the removal was completed. + // Abort the removal. We will do it later. + if (localLOGV) Slog.v(TAG, "completeRemoveProvider: lost the race, " + + "provider still in use"); + return; + } + + mProviderRefCountMap.remove(jBinder); + + Iterator<ProviderClientRecord> iter = mProviderMap.values().iterator(); + while (iter.hasNext()) { + ProviderClientRecord pr = iter.next(); + IBinder myBinder = pr.mProvider.asBinder(); + if (myBinder == jBinder) { + iter.remove(); + if (pr.mLocalProvider == null) { + myBinder.unlinkToDeath(pr, 0); + if (remoteProviderName == null) { + remoteProviderName = pr.mName; + } + } + } } } - - if (name != null) { + + if (remoteProviderName != null) { try { - if(localLOGV) Slog.v(TAG, "removeProvider::Invoking " + - "ActivityManagerNative.removeContentProvider(" + name); + if (localLOGV) { + Slog.v(TAG, "removeProvider: Invoking ActivityManagerNative." + + "removeContentProvider(" + remoteProviderName + ")"); + } ActivityManagerNative.getDefault().removeContentProvider( - getApplicationThread(), name); + getApplicationThread(), remoteProviderName); } catch (RemoteException e) { //do nothing content provider object is dead any way - } //end catch - } - } - - public final String removeProviderLocked(IContentProvider provider) { - if (provider == null) { - return null; + } } - IBinder providerBinder = provider.asBinder(); - - String name = null; - - // remove the provider from mProviderMap - Iterator<ProviderClientRecord> iter = mProviderMap.values().iterator(); - while (iter.hasNext()) { - ProviderClientRecord pr = iter.next(); - IBinder myBinder = pr.mProvider.asBinder(); - if (myBinder == providerBinder) { - //find if its published by this process itself - if(pr.mLocalProvider != null) { - if(localLOGV) Slog.i(TAG, "removeProvider::found local provider returning"); - return name; - } - if(localLOGV) Slog.v(TAG, "removeProvider::Not local provider Unlinking " + - "death recipient"); - //content provider is in another process - myBinder.unlinkToDeath(pr, 0); - iter.remove(); - //invoke remove only once for the very first name seen - if(name == null) { - name = pr.mName; - } - } //end if myBinder - } //end while iter - - return name; } final void removeDeadProvider(String name, IContentProvider provider) { @@ -4151,8 +4175,23 @@ public final class ActivityThread { } } + /** + * Installs the provider. + * + * Providers that are local to the process or that come from the system server + * may be installed permanently which is indicated by setting noReleaseNeeded to true. + * Other remote providers are reference counted. The initial reference count + * for all reference counted providers is one. Providers that are not reference + * counted do not have a reference count (at all). + * + * This method detects when a provider has already been installed. When this happens, + * it increments the reference count of the existing provider (if appropriate) + * and returns the existing provider. This can happen due to concurrent + * attempts to acquire the same provider. + */ private IContentProvider installProvider(Context context, - IContentProvider provider, ProviderInfo info, boolean noisy) { + IContentProvider provider, ProviderInfo info, + boolean noisy, boolean noReleaseNeeded) { ContentProvider localProvider = null; if (provider == null) { if (noisy) { @@ -4210,24 +4249,69 @@ public final class ActivityThread { } synchronized (mProviderMap) { - // Cache the pointer for the remote provider. + // There is a possibility that this thread raced with another thread to + // add the provider. If we find another thread got there first then we + // just get out of the way and return the original provider. + IBinder jBinder = provider.asBinder(); String names[] = PATTERN_SEMICOLON.split(info.authority); - for (int i=0; i<names.length; i++) { - ProviderClientRecord pr = new ProviderClientRecord(names[i], provider, - localProvider); - try { - provider.asBinder().linkToDeath(pr, 0); + for (int i = 0; i < names.length; i++) { + ProviderClientRecord pr = mProviderMap.get(names[i]); + if (pr != null) { + if (localLOGV) { + Slog.v(TAG, "installProvider: lost the race, " + + "using existing named provider"); + } + provider = pr.mProvider; + } else { + pr = new ProviderClientRecord(names[i], provider, localProvider); + if (localProvider == null) { + try { + jBinder.linkToDeath(pr, 0); + } catch (RemoteException e) { + // Provider already dead. Bail out of here without making + // any changes to the provider map or other data structures. + return null; + } + } mProviderMap.put(names[i], pr); - } catch (RemoteException e) { - return null; } } + if (localProvider != null) { - mLocalProviders.put(provider.asBinder(), - new ProviderClientRecord(null, provider, localProvider)); + ProviderClientRecord pr = mLocalProviders.get(jBinder); + if (pr != null) { + if (localLOGV) { + Slog.v(TAG, "installProvider: lost the race, " + + "using existing local provider"); + } + provider = pr.mProvider; + } else { + pr = new ProviderClientRecord(null, provider, localProvider); + mLocalProviders.put(jBinder, pr); + } } - } + if (!noReleaseNeeded) { + ProviderRefCount prc = mProviderRefCountMap.get(jBinder); + if (prc != null) { + if (localLOGV) { + Slog.v(TAG, "installProvider: lost the race, incrementing ref count"); + } + prc.count += 1; + if (prc.count == 1) { + if (localLOGV) { + Slog.v(TAG, "installProvider: " + + "snatched provider from the jaws of death"); + } + // Because the provider previously had a reference count of zero, + // it was scheduled to be removed. Cancel that. + mH.removeMessages(H.REMOVE_PROVIDER, provider); + } + } else { + mProviderRefCountMap.put(jBinder, new ProviderRefCount(1)); + } + } + } return provider; } 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/LoadedApk.java b/core/java/android/app/LoadedApk.java index 522f477..0c6baeb 100644 --- a/core/java/android/app/LoadedApk.java +++ b/core/java/android/app/LoadedApk.java @@ -901,6 +901,7 @@ public final class LoadedApk { private RuntimeException mUnbindLocation; private boolean mDied; + private boolean mForgotten; private static class ConnectionInfo { IBinder binder; @@ -959,6 +960,7 @@ public final class LoadedApk { ci.binder.unlinkToDeath(ci.deathMonitor, 0); } mActiveConnections.clear(); + mForgotten = true; } } @@ -1020,6 +1022,11 @@ public final class LoadedApk { ServiceDispatcher.ConnectionInfo info; synchronized (this) { + if (mForgotten) { + // We unbound before receiving the connection; ignore + // any connection received. + return; + } old = mActiveConnections.get(name); if (old != null && old.binder == service) { // Huh, already have this one. Oh well! 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/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java index f81ea81..b1c1f30 100644 --- a/core/java/android/app/WallpaperManager.java +++ b/core/java/android/app/WallpaperManager.java @@ -212,7 +212,11 @@ public class WallpaperManager { */ mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER); } - + + public Handler getHandler() { + return mHandler; + } + public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) { synchronized (this) { if (mWallpaper != null) { @@ -604,7 +608,7 @@ public class WallpaperManager { // Ignore } } - + /** * Set the position of the current wallpaper within any larger space, when * that wallpaper is visible behind the given window. The X and Y offsets @@ -619,16 +623,26 @@ public class WallpaperManager { * @param yOffset The offset along the Y dimension, from 0 to 1. */ public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) { - try { - //Log.v(TAG, "Sending new wallpaper offsets from app..."); - ViewRootImpl.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( - windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep); - //Log.v(TAG, "...app returning after sending offsets!"); - } catch (RemoteException e) { - // Ignore. - } + final IBinder fWindowToken = windowToken; + final float fXOffset = xOffset; + final float fYOffset = yOffset; + sGlobals.getHandler().post(new Runnable() { + public void run() { + try { + //Log.v(TAG, "Sending new wallpaper offsets from app..."); + ViewRootImpl.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( + fWindowToken, fXOffset, fYOffset, mWallpaperXStep, mWallpaperYStep); + //Log.v(TAG, "...app returning after sending offsets!"); + } catch (RemoteException e) { + // Ignore. + } catch (IllegalArgumentException e) { + // Since this is being posted, it's possible that this windowToken is no longer + // valid, for example, if setWallpaperOffsets is called just before rotation. + } + } + }); } - + /** * For applications that use multiple virtual screens showing a wallpaper, * specify the step size between virtual screens. For example, if the 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/bluetooth/BluetoothDeviceProfileState.java b/core/java/android/bluetooth/BluetoothDeviceProfileState.java index 7addd4a..b1d0070 100644 --- a/core/java/android/bluetooth/BluetoothDeviceProfileState.java +++ b/core/java/android/bluetooth/BluetoothDeviceProfileState.java @@ -86,7 +86,7 @@ public final class BluetoothDeviceProfileState extends StateMachine { private static final int CONNECTION_ACCESS_REQUEST_REPLY = 104; private static final int CONNECTION_ACCESS_REQUEST_EXPIRY = 105; - private static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs + public static final int CONNECT_OTHER_PROFILES_DELAY = 4000; // 4 secs private static final int CONNECTION_ACCESS_REQUEST_EXPIRY_TIMEOUT = 7000; // 7 secs private static final int CONNECTION_ACCESS_UNDEFINED = -1; private static final long INIT_INCOMING_REJECT_TIMER = 1000; // 1 sec diff --git a/core/java/android/bluetooth/IBluetooth.aidl b/core/java/android/bluetooth/IBluetooth.aidl index fefeb93..deea2b8 100644 --- a/core/java/android/bluetooth/IBluetooth.aidl +++ b/core/java/android/bluetooth/IBluetooth.aidl @@ -90,7 +90,7 @@ interface IBluetooth boolean connectHeadset(String address); boolean disconnectHeadset(String address); - boolean notifyIncomingConnection(String address); + boolean notifyIncomingConnection(String address, boolean rejected); // HID profile APIs boolean connectInputDevice(in BluetoothDevice device); 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/content/res/XmlBlock.java b/core/java/android/content/res/XmlBlock.java index ad1bfb2..bea6529 100644 --- a/core/java/android/content/res/XmlBlock.java +++ b/core/java/android/content/res/XmlBlock.java @@ -484,7 +484,7 @@ final class XmlBlock { private final AssetManager mAssets; private final int mNative; - private final StringBlock mStrings; + /*package*/ final StringBlock mStrings; private boolean mOpen = true; private int mOpenCount = 1; @@ -494,9 +494,9 @@ final class XmlBlock { private static final native int nativeGetStringBlock(int obj); private static final native int nativeCreateParseState(int obj); - private static final native int nativeNext(int state); + /*package*/ static final native int nativeNext(int state); private static final native int nativeGetNamespace(int state); - private static final native int nativeGetName(int state); + /*package*/ static final native int nativeGetName(int state); private static final native int nativeGetText(int state); private static final native int nativeGetLineNumber(int state); private static final native int nativeGetAttributeCount(int state); 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..c2a757f 100644 --- a/core/java/android/hardware/Camera.java +++ b/core/java/android/hardware/Camera.java @@ -22,9 +22,12 @@ import android.graphics.ImageFormat; import android.graphics.Point; import android.graphics.Rect; import android.graphics.SurfaceTexture; +import android.media.AudioManager; +import android.media.MediaPlayer; import android.os.Handler; import android.os.Looper; import android.os.Message; +import android.os.SystemProperties; import android.util.Log; import android.view.Surface; import android.view.SurfaceHolder; @@ -154,6 +157,7 @@ public class Camera { private boolean mOneShot; private boolean mWithBuffer; private boolean mFaceDetectionRunning = false; + private boolean mReleased = false; /** * Broadcast Action: A new picture is taken by the camera, and the entry of @@ -303,7 +307,7 @@ public class Camera { } protected void finalize() { - native_release(); + release(); } private native final void native_setup(Object camera_this, int cameraId); @@ -318,6 +322,15 @@ public class Camera { public final void release() { native_release(); mFaceDetectionRunning = false; + if (mCameraSoundPlayers != null) { + for (CameraSoundPlayer csp: mCameraSoundPlayers) { + if (csp != null) { + csp.release(); + } + } + mCameraSoundPlayers = null; + } + mReleased = true; } /** @@ -1098,9 +1111,21 @@ public class Camera { * Parameters#getMaxNumDetectedFaces()} returns a number larger than 0. * If the face detection has started, apps should not call this again. * - * When the face detection is running, {@link Parameters#setWhiteBalance(String)}, + * <p>When the face detection is running, {@link Parameters#setWhiteBalance(String)}, * {@link Parameters#setFocusAreas(List)}, and {@link Parameters#setMeteringAreas(List)} - * have no effect. + * have no effect. The camera uses the detected faces to do auto-white balance, + * auto exposure, and autofocus. + * + * <p>If the apps call {@link #autoFocus(AutoFocusCallback)}, the camera + * will stop sending face callbacks. The last face callback indicates the + * areas used to do autofocus. After focus completes, face detection will + * resume sending face callbacks. If the apps call {@link + * #cancelAutoFocus()}, the face callbacks will also resume.</p> + * + * <p>After calling {@link #takePicture(Camera.ShutterCallback, Camera.PictureCallback, + * Camera.PictureCallback)} or {@link #stopPreview()}, and then resuming + * preview with {@link #startPreview()}, the apps should call this method + * again to resume face detection.</p> * * @throws IllegalArgumentException if the face detection is unsupported. * @throws RuntimeException if the method fails or the face detection is @@ -1150,14 +1175,31 @@ public class Camera { * camera field of view, and (1000, 1000) represents the bottom-right of * the field of view. For example, suppose the size of the viewfinder UI * is 800x480. The rect passed from the driver is (-1000, -1000, 0, 0). - * The corresponding viewfinder rect should be (0, 0, 400, 240). The - * width and height of the rect will not be 0 or negative. The - * coordinates can be smaller than -1000 or bigger than 1000. But at - * least one vertex will be within (-1000, -1000) and (1000, 1000). + * The corresponding viewfinder rect should be (0, 0, 400, 240). It is + * guaranteed left < right and top < bottom. The coordinates can be + * smaller than -1000 or bigger than 1000. But at least one vertex will + * be within (-1000, -1000) and (1000, 1000). * * <p>The direction is relative to the sensor orientation, that is, what * the sensor sees. The direction is not affected by the rotation or - * mirroring of {@link #setDisplayOrientation(int)}.</p> + * mirroring of {@link #setDisplayOrientation(int)}. The face bounding + * rectangle does not provide any information about face orientation.</p> + * + * <p>Here is the matrix to convert driver coordinates to View coordinates + * in pixels.</p> + * <pre> + * Matrix matrix = new Matrix(); + * CameraInfo info = CameraHolder.instance().getCameraInfo()[cameraId]; + * // Need mirror for front camera. + * boolean mirror = (info.facing == CameraInfo.CAMERA_FACING_FRONT); + * matrix.setScale(mirror ? -1 : 1, 1); + * // This is the value for android.hardware.Camera.setDisplayOrientation. + * matrix.postRotate(displayOrientation); + * // Camera driver coordinates range from (-1000, -1000) to (1000, 1000). + * // UI coordinates range from (0, 0) to (width, height). + * matrix.postScale(view.getWidth() / 2000f, view.getHeight() / 2000f); + * matrix.postTranslate(view.getWidth() / 2f, view.getHeight() / 2f); + * </pre> * * @see #startFaceDetection() */ @@ -2354,7 +2396,7 @@ public class Camera { * * <p>The reference code is as follows. * - * <pre> + * <pre> * public void onOrientationChanged(int orientation) { * if (orientation == ORIENTATION_UNKNOWN) return; * android.hardware.Camera.CameraInfo info = @@ -2369,7 +2411,7 @@ public class Camera { * } * mParameters.setRotation(rotation); * } - * </pre> + * </pre> * * @param rotation The rotation angle in degrees relative to the * orientation of the camera. Rotation can only be 0, @@ -3259,7 +3301,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 +3313,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 +3326,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); @@ -3455,4 +3494,194 @@ public class Camera { return result; } }; + + /** + * <p>The set of default system sounds for camera actions. Use this with + * {@link #playSound} to play an appropriate sound when implementing a + * custom still or video recording mechanism through the preview + * callbacks.</p> + * + * <p>There is no need to play sounds when using {@link #takePicture} or + * {@link android.media.MediaRecorder} for still images or video, + * respectively, as these play their own sounds when needed.</p> + * + * @see #playSound + * @hide + */ + public static class Sound { + /** + * The sound used by {@link android.hardware.Camera#takePicture} to + * indicate still image capture. + */ + public static final int SHUTTER_CLICK = 0; + + /** + * A sound to indicate that focusing has completed. Because deciding + * when this occurs is application-dependent, this sound is not used by + * any methods in the Camera class. + */ + public static final int FOCUS_COMPLETE = 1; + + /** + * The sound used by {@link android.media.MediaRecorder#start} to + * indicate the start of video recording. + */ + public static final int START_VIDEO_RECORDING = 2; + + /** + * The sound used by {@link android.media.MediaRecorder#stop} to + * indicate the end of video recording. + */ + public static final int STOP_VIDEO_RECORDING = 3; + + private static final int NUM_SOUNDS = 4; + }; + + /** + * <p>Play one of the predefined platform sounds for camera actions.</p> + * + * <p>Use this method to play a platform-specific sound for various camera + * actions. The sound playing is done asynchronously, with the same behavior + * and content as the sounds played by {@link #takePicture takePicture}, + * {@link android.media.MediaRecorder#start MediaRecorder.start}, and + * {@link android.media.MediaRecorder#stop MediaRecorder.stop}.</p> + * + * <p>Using this method makes it easy to match the default device sounds + * when recording or capturing data through the preview callbacks + * ({@link #setPreviewCallback setPreviewCallback}, + * {@link #setPreviewTexture setPreviewTexture}).</p> + * + * @param soundId The type of sound to play, selected from the options in + * {@link android.hardware.Camera.Sound} + * @see android.hardware.Camera.Sound + * @see #takePicture + * @see android.media.MediaRecorder + * @hide + */ + public void playSound(int soundId) { + if (mReleased) return; + if (mCameraSoundPlayers == null) { + mCameraSoundPlayers = new CameraSoundPlayer[Sound.NUM_SOUNDS]; + } + if (mCameraSoundPlayers[soundId] == null) { + mCameraSoundPlayers[soundId] = new CameraSoundPlayer(soundId); + } + mCameraSoundPlayers[soundId].play(); + } + + private CameraSoundPlayer[] mCameraSoundPlayers; + + private static class CameraSoundPlayer implements Runnable { + private int mSoundId; + private int mAudioStreamType; + private MediaPlayer mPlayer; + private Thread mThread; + private boolean mExit; + private int mPlayCount; + + private static final String mShutterSound = + "/system/media/audio/ui/camera_click.ogg"; + private static final String mFocusSound = + "/system/media/audio/ui/camera_focus.ogg"; + private static final String mVideoStartSound = + "/system/media/audio/ui/VideoRecord.ogg"; + private static final String mVideoStopSound = + "/system/media/audio/ui/VideoRecord.ogg"; + + @Override + public void run() { + String soundFilePath; + switch (mSoundId) { + case Sound.SHUTTER_CLICK: + soundFilePath = mShutterSound; + break; + case Sound.FOCUS_COMPLETE: + soundFilePath = mFocusSound; + break; + case Sound.START_VIDEO_RECORDING: + soundFilePath = mVideoStartSound; + break; + case Sound.STOP_VIDEO_RECORDING: + soundFilePath = mVideoStopSound; + break; + default: + Log.e(TAG, "Unknown sound " + mSoundId + " requested."); + return; + } + mPlayer = new MediaPlayer(); + try { + mPlayer.setAudioStreamType(mAudioStreamType); + mPlayer.setDataSource(soundFilePath); + mPlayer.setLooping(false); + mPlayer.prepare(); + } catch(IOException e) { + Log.e(TAG, "Error setting up sound " + mSoundId, e); + return; + } + + while(true) { + try { + synchronized (this) { + while(true) { + if (mExit) { + return; + } else if (mPlayCount <= 0) { + wait(); + } else { + mPlayCount--; + break; + } + } + } + mPlayer.start(); + } catch (Exception e) { + Log.e(TAG, "Error playing sound " + mSoundId, e); + } + } + } + + public CameraSoundPlayer(int soundId) { + mSoundId = soundId; + if (SystemProperties.get("ro.camera.sound.forced", "0").equals("0")) { + mAudioStreamType = AudioManager.STREAM_MUSIC; + } else { + mAudioStreamType = AudioManager.STREAM_SYSTEM_ENFORCED; + } + } + + public void play() { + if (mThread == null) { + mThread = new Thread(this); + mThread.start(); + } + synchronized (this) { + mPlayCount++; + notifyAll(); + } + } + + public void release() { + if (mThread != null) { + synchronized (this) { + mExit = true; + notifyAll(); + } + try { + mThread.join(); + } catch (InterruptedException e) { + } + mThread = null; + } + if (mPlayer != null) { + mPlayer.release(); + mPlayer = null; + } + } + + @Override + protected void finalize() { + release(); + } + } + } 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..f6e627c 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,24 +462,9 @@ 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); + public NetworkStats subtract(NetworkStats value) throws NonMonotonicException { + return subtract(value, false); } /** @@ -448,16 +472,14 @@ public class NetworkStats implements Parcelable { * 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. + * @param clampNonMonotonic When non-monotonic stats are found, just clamp + * to 0 instead of throwing {@link NonMonotonicException}. */ - private NetworkStats subtract( - NetworkStats value, boolean enforceMonotonic, boolean clampNegative) { + public NetworkStats subtract(NetworkStats value, boolean clampNonMonotonic) + 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 +492,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 +507,18 @@ 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) { + if (clampNonMonotonic) { + entry.rxBytes = Math.max(entry.rxBytes, 0); + entry.rxPackets = Math.max(entry.rxPackets, 0); + entry.txBytes = Math.max(entry.txBytes, 0); + entry.txPackets = Math.max(entry.txPackets, 0); + entry.operations = Math.max(entry.operations, 0); + } else { + throw new NonMonotonicException(this, i, value, j); + } } } @@ -564,12 +584,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 +664,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/INfcAdapter.aidl b/core/java/android/nfc/INfcAdapter.aidl index 016af58..0b93ad0 100644 --- a/core/java/android/nfc/INfcAdapter.aidl +++ b/core/java/android/nfc/INfcAdapter.aidl @@ -32,7 +32,7 @@ import android.nfc.INfcTag; interface INfcAdapter { INfcTag getNfcTagInterface(); - INfcAdapterExtras getNfcAdapterExtrasInterface(); + INfcAdapterExtras getNfcAdapterExtrasInterface(in String pkg); int getState(); boolean disable(); diff --git a/core/java/android/nfc/INfcAdapterExtras.aidl b/core/java/android/nfc/INfcAdapterExtras.aidl index 0c2a2fd..2b9d4f0 100644 --- a/core/java/android/nfc/INfcAdapterExtras.aidl +++ b/core/java/android/nfc/INfcAdapterExtras.aidl @@ -23,10 +23,10 @@ import android.os.Bundle; * {@hide} */ interface INfcAdapterExtras { - Bundle open(IBinder b); - Bundle close(); - Bundle transceive(in byte[] data_in); - int getCardEmulationRoute(); - void setCardEmulationRoute(int route); - void authenticate(in byte[] token); + Bundle open(in String pkg, IBinder b); + Bundle close(in String pkg, IBinder b); + Bundle transceive(in String pkg, in byte[] data_in); + int getCardEmulationRoute(in String pkg); + void setCardEmulationRoute(in String pkg, int route); + void authenticate(in String pkg, in byte[] token); } diff --git a/core/java/android/nfc/NfcAdapter.java b/core/java/android/nfc/NfcAdapter.java index fe0106d..2857ac5 100644 --- a/core/java/android/nfc/NfcAdapter.java +++ b/core/java/android/nfc/NfcAdapter.java @@ -16,6 +16,8 @@ package android.nfc; +import java.util.HashMap; + import android.annotation.SdkConstant; import android.annotation.SdkConstant.SdkConstantType; import android.app.Activity; @@ -197,15 +199,21 @@ public final class NfcAdapter { static INfcTag sTagService; /** - * NfcAdapter is currently a singleton, and does not require a context. - * However all the public API's are future-proofed to require a context. - * If we start using that then we'll need to keep a HashMap of - * Context.getApplicationContext() -> NfcAdapter, such that NfcAdapter - * is a singleton within each application context. + * The NfcAdapter object for each application context. + * There is a 1-1 relationship between application context and + * NfcAdapter object. + */ + static HashMap<Context, NfcAdapter> sNfcAdapters = new HashMap(); //guard by NfcAdapter.class + + /** + * NfcAdapter used with a null context. This ctor was deprecated but we have + * to support it for backwards compatibility. New methods that require context + * might throw when called on the null-context NfcAdapter. */ - static NfcAdapter sSingleton; // protected by NfcAdapter.class + static NfcAdapter sNullContextNfcAdapter; // protected by NfcAdapter.class final NfcActivityManager mNfcActivityManager; + final Context mContext; /** * A callback to be invoked when the system successfully delivers your {@link NdefMessage} @@ -280,12 +288,12 @@ public final class NfcAdapter { } /** - * Returns the singleton, or throws if NFC is not available. + * Returns the NfcAdapter for application context, + * or throws if NFC is not available. + * @hide */ - static synchronized NfcAdapter getSingleton() { + public static synchronized NfcAdapter getNfcAdapter(Context context) { if (!sIsInitialized) { - sIsInitialized = true; - /* is this device meant to have NFC */ if (!hasNfcFeature()) { Log.v(TAG, "this device does not have NFC support"); @@ -303,12 +311,21 @@ public final class NfcAdapter { Log.e(TAG, "could not retrieve NFC Tag service"); throw new UnsupportedOperationException(); } - sSingleton = new NfcAdapter(); + + sIsInitialized = true; + } + if (context == null) { + if (sNullContextNfcAdapter == null) { + sNullContextNfcAdapter = new NfcAdapter(null); + } + return sNullContextNfcAdapter; } - if (sSingleton == null) { - throw new UnsupportedOperationException(); + NfcAdapter adapter = sNfcAdapters.get(context); + if (adapter == null) { + adapter = new NfcAdapter(context); + sNfcAdapters.put(context, adapter); } - return sSingleton; + return adapter; } /** get handle to NFC service interface */ @@ -336,32 +353,45 @@ public final class NfcAdapter { * @return the default NFC adapter, or null if no NFC adapter exists */ public static NfcAdapter getDefaultAdapter(Context context) { + if (context == null) { + throw new IllegalArgumentException("context cannot be null"); + } + context = context.getApplicationContext(); /* use getSystemService() instead of just instantiating to take * advantage of the context's cached NfcManager & NfcAdapter */ NfcManager manager = (NfcManager) context.getSystemService(Context.NFC_SERVICE); + if (manager == null) { + // NFC not available + return null; + } return manager.getDefaultAdapter(); } /** - * Get a handle to the default NFC Adapter on this Android device. - * <p> - * Most Android devices will only have one NFC Adapter (NFC Controller). - * - * @return the default NFC adapter, or null if no NFC adapter exists + * Legacy NfcAdapter getter, always use {@link #getDefaultAdapter(Context)} instead.<p> + * This method was deprecated at API level 10 (Gingerbread MR1) because a context is required + * for many NFC API methods. Those methods will fail when called on an NfcAdapter + * object created from this method.<p> * @deprecated use {@link #getDefaultAdapter(Context)} */ @Deprecated public static NfcAdapter getDefaultAdapter() { Log.w(TAG, "WARNING: NfcAdapter.getDefaultAdapter() is deprecated, use " + "NfcAdapter.getDefaultAdapter(Context) instead", new Exception()); - return getSingleton(); + + return NfcAdapter.getNfcAdapter(null); + } + + NfcAdapter(Context context) { + mContext = context; + mNfcActivityManager = new NfcActivityManager(this); } /** - * Does not currently need a context. + * @hide */ - NfcAdapter() { - mNfcActivityManager = new NfcActivityManager(this); + public Context getContext() { + return mContext; } /** @@ -875,8 +905,12 @@ public final class NfcAdapter { * @hide */ public INfcAdapterExtras getNfcAdapterExtrasInterface() { + if (mContext == null) { + throw new UnsupportedOperationException("You need a context on NfcAdapter to use the " + + " NFC extras APIs"); + } try { - return sService.getNfcAdapterExtrasInterface(); + return sService.getNfcAdapterExtrasInterface(mContext.getPackageName()); } catch (RemoteException e) { attemptDeadServiceRecovery(e); return null; diff --git a/core/java/android/nfc/NfcManager.java b/core/java/android/nfc/NfcManager.java index 300ab45..6ec2e21 100644 --- a/core/java/android/nfc/NfcManager.java +++ b/core/java/android/nfc/NfcManager.java @@ -39,8 +39,9 @@ public final class NfcManager { */ public NfcManager(Context context) { NfcAdapter adapter; + context = context.getApplicationContext(); try { - adapter = NfcAdapter.getSingleton(); + adapter = NfcAdapter.getNfcAdapter(context); } catch (UnsupportedOperationException e) { adapter = null; } 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/RemoteException.java b/core/java/android/os/RemoteException.java index 9d76156..e30d24f 100644 --- a/core/java/android/os/RemoteException.java +++ b/core/java/android/os/RemoteException.java @@ -24,4 +24,8 @@ public class RemoteException extends AndroidException { public RemoteException() { super(); } + + public RemoteException(String message) { + super(message); + } } 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/os/TransactionTooLargeException.java b/core/java/android/os/TransactionTooLargeException.java new file mode 100644 index 0000000..25f09e8 --- /dev/null +++ b/core/java/android/os/TransactionTooLargeException.java @@ -0,0 +1,59 @@ +/* + * 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.os; +import android.os.RemoteException; + +/** + * The Binder transaction failed because it was too large. + * <p> + * During a remote procedure call, the arguments and the return value of the call + * are transferred as {@link Parcel} objects stored in the Binder transaction buffer. + * If the arguments or the return value are too large to fit in the transaction buffer, + * then the call will fail and {@link TransactionTooLargeException} will be thrown. + * </p><p> + * The Binder transaction buffer has a limited fixed size, currently 1Mb, which + * is shared by all transactions in progress for the process. Consequently this + * exception can be thrown when there are many transactions in progress even when + * most of the individual transactions are of moderate size. + * </p><p> + * There are two possible outcomes when a remote procedure call throws + * {@link TransactionTooLargeException}. Either the client was unable to send + * its request to the service (most likely if the arguments were too large to fit in + * the transaction buffer), or the service was unable to send its response back + * to the client (most likely if the return value was too large to fit + * in the transaction buffer). It is not possible to tell which of these outcomes + * actually occurred. The client should assume that a partial failure occurred. + * </p><p> + * The key to avoiding {@link TransactionTooLargeException} is to keep all + * transactions relatively small. Try to minimize the amount of memory needed to create + * a {@link Parcel} for the arguments and the return value of the remote procedure call. + * Avoid transferring huge arrays of strings or large bitmaps. + * If possible, try to break up big requests into smaller pieces. + * </p><p> + * If you are implementing a service, it may help to impose size or complexity + * contraints on the queries that clients can perform. For example, if the result set + * could become large, then don't allow the client to request more than a few records + * at a time. Alternately, instead of returning all of the available data all at once, + * return the essential information first and make the client ask for additional information + * later as needed. + * </p> + */ +public class TransactionTooLargeException extends RemoteException { + public TransactionTooLargeException() { + super(); + } +} 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..83acef8 100644 --- a/core/java/android/provider/ContactsContract.java +++ b/core/java/android/provider/ContactsContract.java @@ -187,6 +187,16 @@ public final class ContactsContract { public static final String DEFERRED_SNIPPETING_QUERY = "deferred_snippeting_query"; /** + * A boolean parameter for {@link CommonDataKinds.Phone#CONTENT_URI}, + * {@link CommonDataKinds.Email#CONTENT_URI}, and + * {@link CommonDataKinds.StructuredPostal#CONTENT_URI}. + * This enables a content provider to remove duplicate entries in results. + * + * @hide + */ + public static final String REMOVE_DUPLICATE_ENTRIES = "remove_duplicate_entries"; + + /** * <p> * API for obtaining a pre-authorized version of a URI that normally requires special * permission (beyond READ_CONTACTS) to read. The caller obtaining the pre-authorized URI @@ -1673,7 +1683,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 +2745,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 +3157,6 @@ public final class ContactsContract { * </pre> * </dd> * </dl> - * @hide */ public static final class StreamItems implements BaseColumns, StreamItemsColumns { /** @@ -3247,7 +3254,6 @@ public final class ContactsContract { * Columns in the StreamItems table. * * @see ContactsContract.StreamItems - * @hide */ protected interface StreamItemsColumns { /** @@ -3538,7 +3544,6 @@ public final class ContactsContract { * <pre> * </dd> * </dl> - * @hide */ public static final class StreamItemPhotos implements BaseColumns, StreamItemPhotosColumns { /** @@ -3566,7 +3571,6 @@ public final class ContactsContract { * Columns in the StreamItemPhotos table. * * @see ContactsContract.StreamItemPhotos - * @hide */ protected interface StreamItemPhotosColumns { /** @@ -4666,6 +4670,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..b032169 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. diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 79995d0..0e6d07d 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()) { diff --git a/core/java/android/server/BluetoothEventLoop.java b/core/java/android/server/BluetoothEventLoop.java index 1b473ec..a2038c9 100644 --- a/core/java/android/server/BluetoothEventLoop.java +++ b/core/java/android/server/BluetoothEventLoop.java @@ -784,11 +784,12 @@ class BluetoothEventLoop { // machine. We don't handle AVCTP signals currently. We only send // intents for AVDTP state changes. We need to handle both of them in // some cases. For now, just don't move to incoming state in this case. - mBluetoothService.notifyIncomingA2dpConnection(address); + mBluetoothService.notifyIncomingA2dpConnection(address, false); } else { Log.i(TAG, "" + authorized + "Incoming A2DP / AVRCP connection from " + address); mA2dp.allowIncomingConnect(device, authorized); + mBluetoothService.notifyIncomingA2dpConnection(address, true); } } else if (BluetoothUuid.isInputDevice(uuid)) { // We can have more than 1 input device connected. diff --git a/core/java/android/server/BluetoothService.java b/core/java/android/server/BluetoothService.java index 5ea8e2a..28e231e 100755 --- a/core/java/android/server/BluetoothService.java +++ b/core/java/android/server/BluetoothService.java @@ -89,7 +89,7 @@ public class BluetoothService extends IBluetooth.Stub { private int mNativeData; private BluetoothEventLoop mEventLoop; - private BluetoothHeadset mBluetoothHeadset; + private BluetoothHeadset mHeadsetProxy; private BluetoothInputDevice mInputDevice; private BluetoothPan mPan; private boolean mIsAirplaneSensitive; @@ -605,6 +605,7 @@ public class BluetoothService extends IBluetooth.Stub { } mBondState.initBondState(); initProfileState(); + getProfileProxy(); } /** @@ -1766,8 +1767,8 @@ public class BluetoothService extends IBluetooth.Stub { private void dumpHeadsetService(PrintWriter pw) { pw.println("\n--Headset Service--"); - if (mBluetoothHeadset != null) { - List<BluetoothDevice> deviceList = mBluetoothHeadset.getConnectedDevices(); + if (mHeadsetProxy != null) { + List<BluetoothDevice> deviceList = mHeadsetProxy.getConnectedDevices(); if (deviceList.size() == 0) { pw.println("No headsets connected"); } else { @@ -1775,21 +1776,20 @@ public class BluetoothService extends IBluetooth.Stub { pw.println("\ngetConnectedDevices[0] = " + device); dumpHeadsetConnectionState(pw, device); pw.println("getBatteryUsageHint() = " + - mBluetoothHeadset.getBatteryUsageHint(device)); + mHeadsetProxy.getBatteryUsageHint(device)); } deviceList.clear(); - deviceList = mBluetoothHeadset.getDevicesMatchingConnectionStates(new int[] { + deviceList = mHeadsetProxy.getDevicesMatchingConnectionStates(new int[] { BluetoothProfile.STATE_CONNECTED, BluetoothProfile.STATE_DISCONNECTED}); pw.println("--Connected and Disconnected Headsets"); for (BluetoothDevice device: deviceList) { pw.println(device); - if (mBluetoothHeadset.isAudioConnected(device)) { + if (mHeadsetProxy.isAudioConnected(device)) { pw.println("SCO audio connected to device:" + device); } } } - mAdapter.closeProfileProxy(BluetoothProfile.HEADSET, mBluetoothHeadset); } private void dumpInputDeviceProfile(PrintWriter pw) { @@ -1824,7 +1824,6 @@ public class BluetoothService extends IBluetooth.Stub { pw.println(device); } } - mAdapter.closeProfileProxy(BluetoothProfile.INPUT_DEVICE, mBluetoothHeadset); } private void dumpPanProfile(PrintWriter pw) { @@ -1862,7 +1861,7 @@ public class BluetoothService extends IBluetooth.Stub { private void dumpHeadsetConnectionState(PrintWriter pw, BluetoothDevice device) { - switch (mBluetoothHeadset.getConnectionState(device)) { + switch (mHeadsetProxy.getConnectionState(device)) { case BluetoothHeadset.STATE_CONNECTING: pw.println("getConnectionState() = STATE_CONNECTING"); break; @@ -1884,7 +1883,6 @@ public class BluetoothService extends IBluetooth.Stub { Integer pid = mServiceRecordToPid.get(handle).first; pw.println("\tpid " + pid + " handle " + Integer.toHexString(handle)); } - mAdapter.closeProfileProxy(BluetoothProfile.PAN, mBluetoothHeadset); } private void dumpAclConnectedDevices(PrintWriter pw) { @@ -1927,11 +1925,16 @@ public class BluetoothService extends IBluetooth.Stub { } } + private void getProfileProxy() { + mAdapter.getProfileProxy(mContext, + mBluetoothProfileServiceListener, BluetoothProfile.HEADSET); + } + private BluetoothProfile.ServiceListener mBluetoothProfileServiceListener = new BluetoothProfile.ServiceListener() { public void onServiceConnected(int profile, BluetoothProfile proxy) { if (profile == BluetoothProfile.HEADSET) { - mBluetoothHeadset = (BluetoothHeadset) proxy; + mHeadsetProxy = (BluetoothHeadset) proxy; } else if (profile == BluetoothProfile.INPUT_DEVICE) { mInputDevice = (BluetoothInputDevice) proxy; } else if (profile == BluetoothProfile.PAN) { @@ -1940,7 +1943,7 @@ public class BluetoothService extends IBluetooth.Stub { } public void onServiceDisconnected(int profile) { if (profile == BluetoothProfile.HEADSET) { - mBluetoothHeadset = null; + mHeadsetProxy = null; } else if (profile == BluetoothProfile.INPUT_DEVICE) { mInputDevice = null; } else if (profile == BluetoothProfile.PAN) { @@ -2424,25 +2427,43 @@ public class BluetoothService extends IBluetooth.Stub { } } - public boolean notifyIncomingConnection(String address) { - BluetoothDeviceProfileState state = - mDeviceProfileState.get(address); + public boolean notifyIncomingConnection(String address, boolean rejected) { + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); if (state != null) { Message msg = new Message(); - msg.what = BluetoothDeviceProfileState.CONNECT_HFP_INCOMING; - state.sendMessage(msg); + if (rejected) { + if (mA2dpService.getPriority(getRemoteDevice(address)) >= + BluetoothProfile.PRIORITY_ON) { + msg.what = BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES; + msg.arg1 = BluetoothDeviceProfileState.CONNECT_A2DP_OUTGOING; + state.sendMessageDelayed(msg, + BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES_DELAY); + } + } else { + msg.what = BluetoothDeviceProfileState.CONNECT_HFP_INCOMING; + state.sendMessage(msg); + } return true; } return false; } - /*package*/ boolean notifyIncomingA2dpConnection(String address) { - BluetoothDeviceProfileState state = - mDeviceProfileState.get(address); + /*package*/ boolean notifyIncomingA2dpConnection(String address, boolean rejected) { + BluetoothDeviceProfileState state = mDeviceProfileState.get(address); if (state != null) { Message msg = new Message(); - msg.what = BluetoothDeviceProfileState.CONNECT_A2DP_INCOMING; - state.sendMessage(msg); + if (rejected) { + if (mHeadsetProxy.getPriority(getRemoteDevice(address)) >= + BluetoothProfile.PRIORITY_ON) { + msg.what = BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES; + msg.arg1 = BluetoothDeviceProfileState.CONNECT_HFP_OUTGOING; + state.sendMessageDelayed(msg, + BluetoothDeviceProfileState.CONNECT_OTHER_PROFILES_DELAY); + } + } else { + msg.what = BluetoothDeviceProfileState.CONNECT_A2DP_INCOMING; + state.sendMessage(msg); + } return true; } return false; 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/AudioMessageParams.java b/core/java/android/speech/tts/AudioMessageParams.java index 68d8738..29b4367 100644 --- a/core/java/android/speech/tts/AudioMessageParams.java +++ b/core/java/android/speech/tts/AudioMessageParams.java @@ -15,12 +15,12 @@ */ package android.speech.tts; -import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher; +import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher; class AudioMessageParams extends MessageParams { private final BlockingMediaPlayer mPlayer; - AudioMessageParams(UtteranceCompletedDispatcher dispatcher, + AudioMessageParams(UtteranceProgressDispatcher dispatcher, String callingApp, BlockingMediaPlayer player) { super(dispatcher, callingApp); mPlayer = player; diff --git a/core/java/android/speech/tts/AudioPlaybackHandler.java b/core/java/android/speech/tts/AudioPlaybackHandler.java index d970ae6..0194240 100644 --- a/core/java/android/speech/tts/AudioPlaybackHandler.java +++ b/core/java/android/speech/tts/AudioPlaybackHandler.java @@ -312,10 +312,11 @@ class AudioPlaybackHandler { private void handleSilence(MessageParams msg) { if (DBG) Log.d(TAG, "handleSilence()"); SilenceMessageParams params = (SilenceMessageParams) msg; + params.getDispatcher().dispatchOnStart(); if (params.getSilenceDurationMs() > 0) { params.getConditionVariable().block(params.getSilenceDurationMs()); } - params.getDispatcher().dispatchUtteranceCompleted(); + params.getDispatcher().dispatchOnDone(); if (DBG) Log.d(TAG, "handleSilence() done."); } @@ -323,11 +324,12 @@ class AudioPlaybackHandler { private void handleAudio(MessageParams msg) { if (DBG) Log.d(TAG, "handleAudio()"); AudioMessageParams params = (AudioMessageParams) msg; + params.getDispatcher().dispatchOnStart(); // Note that the BlockingMediaPlayer spawns a separate thread. // // TODO: This can be avoided. params.getPlayer().startAndWait(); - params.getDispatcher().dispatchUtteranceCompleted(); + params.getDispatcher().dispatchOnDone(); if (DBG) Log.d(TAG, "handleAudio() done."); } @@ -361,6 +363,7 @@ class AudioPlaybackHandler { if (DBG) Log.d(TAG, "Created audio track [" + audioTrack.hashCode() + "]"); param.setAudioTrack(audioTrack); + msg.getDispatcher().dispatchOnStart(); } // More data available to be flushed to the audio track. @@ -411,6 +414,7 @@ class AudioPlaybackHandler { final AudioTrack audioTrack = params.getAudioTrack(); if (audioTrack == null) { + params.getDispatcher().dispatchOnError(); return; } @@ -439,7 +443,7 @@ class AudioPlaybackHandler { audioTrack.release(); params.setAudioTrack(null); } - params.getDispatcher().dispatchUtteranceCompleted(); + params.getDispatcher().dispatchOnDone(); mLastSynthesisRequest = null; params.mLogger.onWriteData(); } 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/ITextToSpeechCallback.aidl b/core/java/android/speech/tts/ITextToSpeechCallback.aidl index 40902ae..f0287d4 100755 --- a/core/java/android/speech/tts/ITextToSpeechCallback.aidl +++ b/core/java/android/speech/tts/ITextToSpeechCallback.aidl @@ -21,5 +21,7 @@ package android.speech.tts; * {@hide} */ oneway interface ITextToSpeechCallback { - void utteranceCompleted(String utteranceId); + void onStart(String utteranceId); + void onDone(String utteranceId); + void onError(String utteranceId); } 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/MessageParams.java b/core/java/android/speech/tts/MessageParams.java index e7d6da3..de9cc07 100644 --- a/core/java/android/speech/tts/MessageParams.java +++ b/core/java/android/speech/tts/MessageParams.java @@ -15,22 +15,22 @@ */ package android.speech.tts; -import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher; +import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher; abstract class MessageParams { static final int TYPE_SYNTHESIS = 1; static final int TYPE_AUDIO = 2; static final int TYPE_SILENCE = 3; - private final UtteranceCompletedDispatcher mDispatcher; + private final UtteranceProgressDispatcher mDispatcher; private final String mCallingApp; - MessageParams(UtteranceCompletedDispatcher dispatcher, String callingApp) { + MessageParams(UtteranceProgressDispatcher dispatcher, String callingApp) { mDispatcher = dispatcher; mCallingApp = callingApp; } - UtteranceCompletedDispatcher getDispatcher() { + UtteranceProgressDispatcher getDispatcher() { return mDispatcher; } diff --git a/core/java/android/speech/tts/PlaybackSynthesisCallback.java b/core/java/android/speech/tts/PlaybackSynthesisCallback.java index 0cca06a..ce3522b 100644 --- a/core/java/android/speech/tts/PlaybackSynthesisCallback.java +++ b/core/java/android/speech/tts/PlaybackSynthesisCallback.java @@ -15,7 +15,7 @@ */ package android.speech.tts; -import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher; +import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher; import android.util.Log; /** @@ -62,12 +62,12 @@ class PlaybackSynthesisCallback extends AbstractSynthesisCallback { private volatile boolean mDone = false; - private final UtteranceCompletedDispatcher mDispatcher; + private final UtteranceProgressDispatcher mDispatcher; private final String mCallingApp; private final EventLogger mLogger; PlaybackSynthesisCallback(int streamType, float volume, float pan, - AudioPlaybackHandler audioTrackHandler, UtteranceCompletedDispatcher dispatcher, + AudioPlaybackHandler audioTrackHandler, UtteranceProgressDispatcher dispatcher, String callingApp, EventLogger logger) { mStreamType = streamType; mVolume = volume; diff --git a/core/java/android/speech/tts/SilenceMessageParams.java b/core/java/android/speech/tts/SilenceMessageParams.java index 7a4ff1c..9909126 100644 --- a/core/java/android/speech/tts/SilenceMessageParams.java +++ b/core/java/android/speech/tts/SilenceMessageParams.java @@ -16,13 +16,13 @@ package android.speech.tts; import android.os.ConditionVariable; -import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher; +import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher; class SilenceMessageParams extends MessageParams { private final ConditionVariable mCondVar = new ConditionVariable(); private final long mSilenceDurationMs; - SilenceMessageParams(UtteranceCompletedDispatcher dispatcher, + SilenceMessageParams(UtteranceProgressDispatcher dispatcher, String callingApp, long silenceDurationMs) { super(dispatcher, callingApp); mSilenceDurationMs = silenceDurationMs; diff --git a/core/java/android/speech/tts/SynthesisMessageParams.java b/core/java/android/speech/tts/SynthesisMessageParams.java index 779721e..0c0f033 100644 --- a/core/java/android/speech/tts/SynthesisMessageParams.java +++ b/core/java/android/speech/tts/SynthesisMessageParams.java @@ -17,7 +17,7 @@ package android.speech.tts; import android.media.AudioFormat; import android.media.AudioTrack; -import android.speech.tts.TextToSpeechService.UtteranceCompletedDispatcher; +import android.speech.tts.TextToSpeechService.UtteranceProgressDispatcher; import java.util.LinkedList; @@ -56,7 +56,7 @@ final class SynthesisMessageParams extends MessageParams { SynthesisMessageParams(int streamType, int sampleRate, int audioFormat, int channelCount, - float volume, float pan, UtteranceCompletedDispatcher dispatcher, + float volume, float pan, UtteranceProgressDispatcher dispatcher, String callingApp, EventLogger logger) { super(dispatcher, callingApp); diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 98ab310..38699ea 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; @@ -442,7 +482,7 @@ public class TextToSpeech { private OnInitListener mInitListener; // Written from an unspecified application thread, read from // a binder thread. - private volatile OnUtteranceCompletedListener mUtteranceCompletedListener; + private volatile UtteranceProgressListener mUtteranceProgressListener; private final Object mStartLock = new Object(); private String mRequestedEngine; @@ -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. @@ -1072,9 +1146,28 @@ public class TextToSpeech { * @param listener The listener to use. * * @return {@link #ERROR} or {@link #SUCCESS}. + * + * @deprecated Use {@link #setOnUtteranceProgressListener(UtteranceProgressListener)} + * instead. */ + @Deprecated public int setOnUtteranceCompletedListener(final OnUtteranceCompletedListener listener) { - mUtteranceCompletedListener = listener; + mUtteranceProgressListener = UtteranceProgressListener.from(listener); + return TextToSpeech.SUCCESS; + } + + /** + * Sets the listener that will be notified of various events related to the + * synthesis of a given utterance. + * + * See {@link UtteranceProgressListener} and + * {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}. + * + * @param listener the listener to use. + * @return {@link #ERROR} or {@link #SUCCESS} + */ + public int setOnUtteranceProgressListener(UtteranceProgressListener listener) { + mUtteranceProgressListener = listener; return TextToSpeech.SUCCESS; } @@ -1130,10 +1223,26 @@ public class TextToSpeech { private ITextToSpeechService mService; private final ITextToSpeechCallback.Stub mCallback = new ITextToSpeechCallback.Stub() { @Override - public void utteranceCompleted(String utteranceId) { - OnUtteranceCompletedListener listener = mUtteranceCompletedListener; + public void onDone(String utteranceId) { + UtteranceProgressListener listener = mUtteranceProgressListener; + if (listener != null) { + listener.onDone(utteranceId); + } + } + + @Override + public void onError(String utteranceId) { + UtteranceProgressListener listener = mUtteranceProgressListener; + if (listener != null) { + listener.onError(utteranceId); + } + } + + @Override + public void onStart(String utteranceId) { + UtteranceProgressListener listener = mUtteranceProgressListener; if (listener != null) { - listener.onUtteranceCompleted(utteranceId); + listener.onStart(utteranceId); } } }; diff --git a/core/java/android/speech/tts/TextToSpeechService.java b/core/java/android/speech/tts/TextToSpeechService.java index 48739ba..39922da 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); } @@ -294,6 +306,7 @@ public abstract class TextToSpeechService extends Service { */ public int enqueueSpeechItem(int queueMode, final SpeechItem speechItem) { if (!speechItem.isValid()) { + speechItem.dispatchOnError(); return TextToSpeech.ERROR; } @@ -320,6 +333,7 @@ public abstract class TextToSpeechService extends Service { return TextToSpeech.SUCCESS; } else { Log.w(TAG, "SynthThread has quit"); + speechItem.dispatchOnError(); return TextToSpeech.ERROR; } } @@ -369,14 +383,16 @@ public abstract class TextToSpeechService extends Service { } } - interface UtteranceCompletedDispatcher { - public void dispatchUtteranceCompleted(); + interface UtteranceProgressDispatcher { + public void dispatchOnDone(); + public void dispatchOnStart(); + public void dispatchOnError(); } /** * An item in the synth thread queue. */ - private abstract class SpeechItem implements UtteranceCompletedDispatcher { + private abstract class SpeechItem implements UtteranceProgressDispatcher { private final String mCallingApp; protected final Bundle mParams; private boolean mStarted = false; @@ -431,10 +447,27 @@ public abstract class TextToSpeechService extends Service { stopImpl(); } - public void dispatchUtteranceCompleted() { + @Override + public void dispatchOnDone() { final String utteranceId = getUtteranceId(); if (!TextUtils.isEmpty(utteranceId)) { - mCallbacks.dispatchUtteranceCompleted(getCallingApp(), utteranceId); + mCallbacks.dispatchOnDone(getCallingApp(), utteranceId); + } + } + + @Override + public void dispatchOnStart() { + final String utteranceId = getUtteranceId(); + if (!TextUtils.isEmpty(utteranceId)) { + mCallbacks.dispatchOnStart(getCallingApp(), utteranceId); + } + } + + @Override + public void dispatchOnError() { + final String utteranceId = getUtteranceId(); + if (!TextUtils.isEmpty(utteranceId)) { + mCallbacks.dispatchOnError(getCallingApp(), utteranceId); } } @@ -605,9 +638,12 @@ public abstract class TextToSpeechService extends Service { @Override protected int playImpl() { + dispatchOnStart(); int status = super.playImpl(); if (status == TextToSpeech.SUCCESS) { - dispatchUtteranceCompleted(); + dispatchOnDone(); + } else { + dispatchOnError(); } return status; } @@ -778,6 +814,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. @@ -832,16 +880,34 @@ public abstract class TextToSpeechService extends Service { } } - public void dispatchUtteranceCompleted(String packageName, String utteranceId) { - ITextToSpeechCallback cb; - synchronized (mAppToCallback) { - cb = mAppToCallback.get(packageName); + public void dispatchOnDone(String packageName, String utteranceId) { + ITextToSpeechCallback cb = getCallbackFor(packageName); + if (cb == null) return; + try { + cb.onDone(utteranceId); + } catch (RemoteException e) { + Log.e(TAG, "Callback onDone failed: " + e); + } + } + + public void dispatchOnStart(String packageName, String utteranceId) { + ITextToSpeechCallback cb = getCallbackFor(packageName); + if (cb == null) return; + try { + cb.onStart(utteranceId); + } catch (RemoteException e) { + Log.e(TAG, "Callback onStart failed: " + e); } + + } + + public void dispatchOnError(String packageName, String utteranceId) { + ITextToSpeechCallback cb = getCallbackFor(packageName); if (cb == null) return; try { - cb.utteranceCompleted(utteranceId); + cb.onError(utteranceId); } catch (RemoteException e) { - Log.e(TAG, "Callback failed: " + e); + Log.e(TAG, "Callback onError failed: " + e); } } @@ -862,6 +928,15 @@ public abstract class TextToSpeechService extends Service { } } + private ITextToSpeechCallback getCallbackFor(String packageName) { + ITextToSpeechCallback cb; + synchronized (mAppToCallback) { + cb = mAppToCallback.get(packageName); + } + + return cb; + } + } } diff --git a/core/java/android/speech/tts/UtteranceProgressListener.java b/core/java/android/speech/tts/UtteranceProgressListener.java new file mode 100644 index 0000000..a04458a --- /dev/null +++ b/core/java/android/speech/tts/UtteranceProgressListener.java @@ -0,0 +1,68 @@ +// Copyright 2011 Google Inc. All Rights Reserved. + +package android.speech.tts; + +/** + * Listener for events relating to the progress of an utterance through + * the synthesis queue. Each utterance is associated with a call to + * {@link TextToSpeech#speak} or {@link TextToSpeech#synthesizeToFile} with an + * associated utterance identifier, as per {@link TextToSpeech.Engine#KEY_PARAM_UTTERANCE_ID}. + * + * The callbacks specified in this method can be called from multiple threads. + */ +public abstract class UtteranceProgressListener { + /** + * Called when an utterance "starts" as perceived by the caller. This will + * be soon before audio is played back in the case of a {@link TextToSpeech#speak} + * or before the first bytes of a file are written to storage in the case + * of {@link TextToSpeech#synthesizeToFile}. + * + * @param utteranceId the utterance ID of the utterance. + */ + public abstract void onStart(String utteranceId); + + /** + * Called when an utterance has successfully completed processing. + * All audio will have been played back by this point for audible output, and all + * output will have been written to disk for file synthesis requests. + * + * This request is guaranteed to be called after {@link #onStart(String)}. + * + * @param utteranceId the utterance ID of the utterance. + */ + public abstract void onDone(String utteranceId); + + /** + * Called when an error has occurred during processing. This can be called + * at any point in the synthesis process. Note that there might be calls + * to {@link #onStart(String)} for specified utteranceId but there will never + * be a call to both {@link #onDone(String)} and {@link #onError(String)} for + * the same utterance. + * + * @param utteranceId the utterance ID of the utterance. + */ + public abstract void onError(String utteranceId); + + /** + * Wraps an old deprecated OnUtteranceCompletedListener with a shiny new + * progress listener. + * + * @hide + */ + static UtteranceProgressListener from( + final TextToSpeech.OnUtteranceCompletedListener listener) { + return new UtteranceProgressListener() { + @Override + public synchronized void onDone(String utteranceId) { + listener.onUtteranceCompleted(utteranceId); + } + + // The following methods are left unimplemented. + @Override + public void onStart(String utteranceId) { } + + @Override + public void onError(String utteranceId) { } + }; + } +} 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/GLES20Canvas.java b/core/java/android/view/GLES20Canvas.java index c934c7b..d948ec2 100644 --- a/core/java/android/view/GLES20Canvas.java +++ b/core/java/android/view/GLES20Canvas.java @@ -315,6 +315,27 @@ class GLES20Canvas extends HardwareCanvas { private static native void nFlushCaches(int level); + /** + * Release all resources associated with the underlying caches. This should + * only be called after a full flushCaches(). + * + * @hide + */ + public static void terminateCaches() { + nTerminateCaches(); + } + + private static native void nTerminateCaches(); + + /** + * @hide + */ + public static void initCaches() { + nInitCaches(); + } + + private static native void nInitCaches(); + /////////////////////////////////////////////////////////////////////////// // Display list /////////////////////////////////////////////////////////////////////////// diff --git a/core/java/android/view/HardwareRenderer.java b/core/java/android/view/HardwareRenderer.java index b86d21d..8e39d6e 100644 --- a/core/java/android/view/HardwareRenderer.java +++ b/core/java/android/view/HardwareRenderer.java @@ -22,9 +22,13 @@ import android.graphics.Paint; import android.graphics.Rect; import android.graphics.SurfaceTexture; import android.opengl.GLUtils; +import android.opengl.ManagedEGLContext; +import android.os.Handler; +import android.os.Looper; import android.os.SystemClock; import android.os.SystemProperties; import android.util.Log; +import com.google.android.gles_jni.EGLImpl; import javax.microedition.khronos.egl.EGL10; import javax.microedition.khronos.egl.EGL11; @@ -34,6 +38,8 @@ import javax.microedition.khronos.egl.EGLDisplay; import javax.microedition.khronos.egl.EGLSurface; import javax.microedition.khronos.opengles.GL; +import java.io.File; + import static javax.microedition.khronos.egl.EGL10.*; /** @@ -45,6 +51,11 @@ public abstract class HardwareRenderer { static final String LOG_TAG = "HardwareRenderer"; /** + * Name of the file that holds the shaders cache. + */ + private static final String CACHE_PATH_SHADERS = "com.android.opengl.shaders_cache"; + + /** * Turn on to only refresh the parts of the screen that need updating. * When turned on the property defined by {@link #RENDER_DIRTY_REGIONS_PROPERTY} * must also have the value "true". @@ -200,6 +211,18 @@ public abstract class HardwareRenderer { abstract int getHeight(); /** + * Sets the directory to use as a persistent storage for hardware rendering + * resources. + * + * @param cacheDir A directory the current process can write to + */ + public static void setupDiskCache(File cacheDir) { + nSetupShadersDiskCache(new File(cacheDir, CACHE_PATH_SHADERS).getAbsolutePath()); + } + + private static native void nSetupShadersDiskCache(String cacheFile); + + /** * Interface used to receive callbacks whenever a view is drawn by * a hardware renderer instance. */ @@ -325,6 +348,15 @@ public abstract class HardwareRenderer { } /** + * Invoke this method when the system needs to clean up all resources + * associated with hardware rendering. + */ + static void terminate() { + Log.d(LOG_TAG, "Terminating hardware rendering"); + Gl20Renderer.terminate(); + } + + /** * Indicates whether hardware acceleration is currently enabled. * * @return True if hardware acceleration is in use, false otherwise. @@ -380,7 +412,8 @@ public abstract class HardwareRenderer { static final Object[] sEglLock = new Object[0]; int mWidth = -1, mHeight = -1; - static final ThreadLocal<EGLContext> sEglContextStorage = new ThreadLocal<EGLContext>(); + static final ThreadLocal<Gl20Renderer.MyEGLContext> sEglContextStorage + = new ThreadLocal<Gl20Renderer.MyEGLContext>(); EGLContext mEglContext; Thread mEglThread; @@ -532,12 +565,13 @@ public abstract class HardwareRenderer { } } - mEglContext = sEglContextStorage.get(); + Gl20Renderer.MyEGLContext managedContext = sEglContextStorage.get(); + mEglContext = managedContext != null ? managedContext.getContext() : null; mEglThread = Thread.currentThread(); if (mEglContext == null) { mEglContext = createContext(sEgl, sEglDisplay, sEglConfig); - sEglContextStorage.set(mEglContext); + sEglContextStorage.set(new Gl20Renderer.MyEGLContext(mEglContext)); } } @@ -632,6 +666,8 @@ public abstract class HardwareRenderer { throw new Surface.OutOfResourcesException("eglMakeCurrent failed " + GLUtils.getEGLErrorString(sEgl.eglGetError())); } + + initCaches(); // If mDirtyRegions is set, this means we have an EGL configuration // with EGL_SWAP_BEHAVIOR_PRESERVED_BIT set @@ -652,6 +688,8 @@ public abstract class HardwareRenderer { return mEglContext.getGL(); } + abstract void initCaches(); + EGLContext createContext(EGL10 egl, EGLDisplay eglDisplay, EGLConfig eglConfig) { int[] attribs = { EGL_CONTEXT_CLIENT_VERSION, mGlVersion, EGL_NONE }; @@ -871,6 +909,51 @@ public abstract class HardwareRenderer { private static EGLSurface sPbuffer; private static final Object[] sPbufferLock = new Object[0]; + static class MyEGLContext extends ManagedEGLContext { + final Handler mHandler = new Handler(); + + public MyEGLContext(EGLContext context) { + super(context); + } + + @Override + public void onTerminate(final EGLContext eglContext) { + // Make sure we do this on the correct thread. + if (mHandler.getLooper() != Looper.myLooper()) { + mHandler.post(new Runnable() { + @Override public void run() { + onTerminate(eglContext); + } + }); + return; + } + + synchronized (sEglLock) { + if (sEgl == null) return; + + if (EGLImpl.getInitCount(sEglDisplay) == 1) { + usePbufferSurface(eglContext); + GLES20Canvas.terminateCaches(); + + sEgl.eglDestroyContext(sEglDisplay, eglContext); + sEglContextStorage.remove(); + + sEgl.eglDestroySurface(sEglDisplay, sPbuffer); + sEgl.eglMakeCurrent(sEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT); + + sEgl.eglReleaseThread(); + sEgl.eglTerminate(sEglDisplay); + + sEgl = null; + sEglDisplay = null; + sEglConfig = null; + sPbuffer = null; + sEglContextStorage.set(null); + } + } + } + } + Gl20Renderer(boolean translucent) { super(2, translucent); } @@ -895,6 +978,11 @@ public abstract class HardwareRenderer { EGL_NONE }; } + + @Override + void initCaches() { + GLES20Canvas.initCaches(); + } @Override boolean canDraw() { @@ -982,21 +1070,12 @@ public abstract class HardwareRenderer { static void trimMemory(int level) { if (sEgl == null || sEglConfig == null) return; - EGLContext eglContext = sEglContextStorage.get(); + Gl20Renderer.MyEGLContext managedContext = sEglContextStorage.get(); // We do not have OpenGL objects - if (eglContext == null) { + if (managedContext == null) { return; } else { - synchronized (sPbufferLock) { - // Create a temporary 1x1 pbuffer so we have a context - // to clear our OpenGL objects - if (sPbuffer == null) { - sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] { - EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE - }); - } - } - sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext); + usePbufferSurface(managedContext.getContext()); } switch (level) { @@ -1010,5 +1089,18 @@ public abstract class HardwareRenderer { break; } } + + private static void usePbufferSurface(EGLContext eglContext) { + synchronized (sPbufferLock) { + // Create a temporary 1x1 pbuffer so we have a context + // to clear our OpenGL objects + if (sPbuffer == null) { + sPbuffer = sEgl.eglCreatePbufferSurface(sEglDisplay, sEglConfig, new int[] { + EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE + }); + } + } + sEgl.eglMakeCurrent(sEglDisplay, sPbuffer, sPbuffer, eglContext); + } } } 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/InputDevice.java b/core/java/android/view/InputDevice.java index bfc7c31..8115b36 100755 --- a/core/java/android/view/InputDevice.java +++ b/core/java/android/view/InputDevice.java @@ -19,7 +19,6 @@ package android.view; import android.os.Parcel; import android.os.Parcelable; import android.os.RemoteException; -import android.os.ServiceManager; import java.util.ArrayList; import java.util.List; @@ -44,6 +43,7 @@ public final class InputDevice implements Parcelable { private String mName; private int mSources; private int mKeyboardType; + private String mKeyCharacterMapFile; private final ArrayList<MotionRange> mMotionRanges = new ArrayList<MotionRange>(); @@ -360,6 +360,10 @@ public final class InputDevice implements Parcelable { return KeyCharacterMap.load(mId); } + String getKeyCharacterMapFile() { + return mKeyCharacterMapFile; + } + /** * Gets information about the range of values for a particular {@link MotionEvent} axis. * If the device supports multiple sources, the same axis may have different meanings @@ -532,6 +536,7 @@ public final class InputDevice implements Parcelable { mName = in.readString(); mSources = in.readInt(); mKeyboardType = in.readInt(); + mKeyCharacterMapFile = in.readString(); for (;;) { int axis = in.readInt(); @@ -549,6 +554,7 @@ public final class InputDevice implements Parcelable { out.writeString(mName); out.writeInt(mSources); out.writeInt(mKeyboardType); + out.writeString(mKeyCharacterMapFile); final int numRanges = mMotionRanges.size(); for (int i = 0; i < numRanges; i++) { @@ -587,6 +593,8 @@ public final class InputDevice implements Parcelable { } description.append("\n"); + description.append(" Key Character Map: ").append(mKeyCharacterMapFile).append("\n"); + description.append(" Sources: 0x").append(Integer.toHexString(mSources)).append(" ("); appendSourceDescriptionIfApplicable(description, SOURCE_KEYBOARD, "keyboard"); appendSourceDescriptionIfApplicable(description, SOURCE_DPAD, "dpad"); diff --git a/core/java/android/view/KeyCharacterMap.java b/core/java/android/view/KeyCharacterMap.java index 885a75f..575af3b 100644 --- a/core/java/android/view/KeyCharacterMap.java +++ b/core/java/android/view/KeyCharacterMap.java @@ -20,7 +20,6 @@ import android.text.method.MetaKeyKeyListener; import android.util.AndroidRuntimeException; import android.util.SparseIntArray; import android.os.RemoteException; -import android.os.ServiceManager; import android.util.SparseArray; import java.lang.Character; @@ -140,7 +139,7 @@ public class KeyCharacterMap { private final int mDeviceId; private int mPtr; - private static native int nativeLoad(int id); + private static native int nativeLoad(String file); private static native void nativeDispose(int ptr); private static native char nativeGetCharacter(int ptr, int keyCode, int metaState); @@ -178,7 +177,17 @@ public class KeyCharacterMap { synchronized (sInstances) { KeyCharacterMap map = sInstances.get(deviceId); if (map == null) { - int ptr = nativeLoad(deviceId); // might throw + String kcm = null; + if (deviceId != VIRTUAL_KEYBOARD) { + InputDevice device = InputDevice.getDevice(deviceId); + if (device != null) { + kcm = device.getKeyCharacterMapFile(); + } + } + if (kcm == null || kcm.length() == 0) { + kcm = "/system/usr/keychars/Virtual.kcm"; + } + int ptr = nativeLoad(kcm); // might throw map = new KeyCharacterMap(deviceId, ptr); sInstances.put(deviceId, map); } diff --git a/core/java/android/view/View.java b/core/java/android/view/View.java index 61b13d5..dc46d42 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); @@ -11327,8 +11402,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * {@link SystemClock#uptimeMillis} timebase. */ public void scheduleDrawable(Drawable who, Runnable what, long when) { - if (verifyDrawable(who) && what != null && mAttachInfo != null) { - mAttachInfo.mHandler.postAtTime(what, who, when); + if (verifyDrawable(who) && what != null) { + if (mAttachInfo != null) { + mAttachInfo.mHandler.postAtTime(what, who, when); + } else { + ViewRootImpl.getRunQueue().postDelayed(what, when - SystemClock.uptimeMillis()); + } } } @@ -11339,8 +11418,12 @@ public class View implements Drawable.Callback, Drawable.Callback2, KeyEvent.Cal * @param what the action to cancel */ public void unscheduleDrawable(Drawable who, Runnable what) { - if (verifyDrawable(who) && what != null && mAttachInfo != null) { - mAttachInfo.mHandler.removeCallbacks(what, who); + if (verifyDrawable(who) && what != null) { + if (mAttachInfo != null) { + mAttachInfo.mHandler.removeCallbacks(what, who); + } else { + ViewRootImpl.getRunQueue().removeCallbacks(what); + } } } @@ -13052,7 +13135,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 +13146,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 +13420,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..b15b155 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); @@ -569,7 +567,7 @@ public final class ViewRootImpl extends Handler implements ViewParent, } } - private void destroyHardwareResources() { + void destroyHardwareResources() { if (mAttachInfo.mHardwareRenderer != null) { if (mAttachInfo.mHardwareRenderer.isEnabled()) { mAttachInfo.mHardwareRenderer.destroyLayers(mView); @@ -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"); @@ -865,12 +880,10 @@ public final class ViewRootImpl extends Handler implements ViewParent, || mNewSurfaceNeeded; WindowManager.LayoutParams params = null; - int windowAttributesChanges = 0; if (mWindowAttributesChanged) { mWindowAttributesChanged = false; surfaceChanged = true; params = lp; - windowAttributesChanges = mWindowAttributesChangesFlag; } CompatibilityInfo compatibilityInfo = mCompatibilityInfo.get(); if (compatibilityInfo.supportsScreen() == mLastInCompatMode) { @@ -2336,6 +2349,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 +2402,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 +2463,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 +3545,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 +3763,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 +3781,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 +3877,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 +3895,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 +4564,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 +4586,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 +4597,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 +4608,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/WindowManagerImpl.java b/core/java/android/view/WindowManagerImpl.java index 5ef4f3e..660e3f4 100644 --- a/core/java/android/view/WindowManagerImpl.java +++ b/core/java/android/view/WindowManagerImpl.java @@ -16,9 +16,12 @@ package android.view; +import android.app.ActivityManager; +import android.content.ComponentCallbacks2; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.PixelFormat; +import android.opengl.ManagedEGLContext; import android.os.IBinder; import android.util.AndroidRuntimeException; import android.util.Log; @@ -409,7 +412,30 @@ public class WindowManagerImpl implements WindowManager { */ public void trimMemory(int level) { if (HardwareRenderer.isAvailable()) { - HardwareRenderer.trimMemory(level); + switch (level) { + case ComponentCallbacks2.TRIM_MEMORY_COMPLETE: + case ComponentCallbacks2.TRIM_MEMORY_MODERATE: + // On low and medium end gfx devices + if (!ActivityManager.isHighEndGfx(getDefaultDisplay())) { + // Force a full memory flush + HardwareRenderer.trimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE); + // Destroy all hardware surfaces and resources associated to + // known windows + synchronized (this) { + if (mViews == null) return; + int count = mViews.length; + for (int i = 0; i < count; i++) { + mRoots[i].destroyHardwareResources(); + } + } + // Terminate the hardware renderer to free all resources + ManagedEGLContext.doTerminate(); + break; + } + // high end gfx devices fall through to next case + default: + HardwareRenderer.trimMemory(level); + } } } 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..489587e 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; @@ -146,12 +146,20 @@ public class SpellCheckerSession { } /** + * Cancel pending and running spell check tasks + */ + public void cancel() { + mSpellCheckerSessionListenerImpl.cancel(); + } + + /** * Finish this session and allow TextServicesManagerService to disconnect the bound spell * checker. */ public void close() { mIsUsed = false; try { + mSpellCheckerSessionListenerImpl.close(); mTextServicesManager.finishSpellCheckerService(mSpellCheckerSessionListenerImpl); } catch (RemoteException e) { // do nothing @@ -190,9 +198,10 @@ 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; + private Handler mHandler; private boolean mOpened; private ISpellCheckerSession mISpellCheckerSession; @@ -224,6 +233,9 @@ public class SpellCheckerSession { case TASK_GET_SUGGESTIONS_MULTIPLE: processGetSuggestionsMultiple(scp); break; + case TASK_CLOSE: + processClose(); + break; } } @@ -237,6 +249,13 @@ public class SpellCheckerSession { } } + public void cancel() { + if (DBG) { + Log.w(TAG, "cancel"); + } + processOrEnqueueTask(new SpellCheckerParams(TASK_CANCEL, null, 0, false)); + } + public void getSuggestionsMultiple( TextInfo[] textInfos, int suggestionsLimit, boolean sequentialWords) { if (DBG) { @@ -247,6 +266,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; } @@ -263,8 +289,22 @@ public class SpellCheckerSession { if (DBG) { Log.d(TAG, "process or enqueue task: " + mISpellCheckerSession); } + SpellCheckerParams closeTask = null; if (mISpellCheckerSession == null) { + if (scp.mWhat == TASK_CANCEL) { + while (!mPendingTasks.isEmpty()) { + final SpellCheckerParams tmp = mPendingTasks.poll(); + if (tmp.mWhat == TASK_CLOSE) { + // Only one close task should be processed, while we need to remove all + // close tasks from the queue + closeTask = tmp; + } + } + } mPendingTasks.offer(scp); + if (closeTask != null) { + mPendingTasks.offer(closeTask); + } } else { processTask(scp); } @@ -284,6 +324,22 @@ public class SpellCheckerSession { } } + private void processClose() { + if (!checkOpenConnection()) { + return; + } + if (DBG) { + Log.w(TAG, "Close spell checker tasks."); + } + try { + mISpellCheckerSession.onClose(); + mISpellCheckerSession = null; + mHandler = null; + } catch (RemoteException e) { + Log.e(TAG, "Failed to close " + e); + } + } + private void processGetSuggestionsMultiple(SpellCheckerParams scp) { if (!checkOpenConnection()) { return; @@ -316,13 +372,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..c194559 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; } @@ -1411,4 +1404,17 @@ class BrowserFrame extends Handler { native void nativeSslClientCert(int handle, byte[] pkcs8EncodedPrivateKey, byte[][] asn1DerEncodedCertificateChain); + + /** + * Returns true when the contents of the frame is an RTL or vertical-rl + * page. This is used for determining whether a frame should be initially + * scrolled right-most as opposed to left-most. + * @return true when the frame should be initially scrolled right-most + * based on the text direction and writing mode. + */ + /* package */ boolean getShouldStartScrolledRight() { + return nativeGetShouldStartScrolledRight(mNativeFrame); + } + + private native boolean nativeGetShouldStartScrolledRight(int nativeBrowserFrame); } diff --git a/core/java/android/webkit/FindActionModeCallback.java b/core/java/android/webkit/FindActionModeCallback.java index b85fd17..fffa90b 100644 --- a/core/java/android/webkit/FindActionModeCallback.java +++ b/core/java/android/webkit/FindActionModeCallback.java @@ -18,6 +18,8 @@ package android.webkit; import android.content.Context; import android.content.res.Resources; +import android.graphics.Point; +import android.graphics.Rect; import android.text.Editable; import android.text.Selection; import android.text.Spannable; @@ -254,13 +256,18 @@ class FindActionModeCallback implements ActionMode.Callback, TextWatcher, // Does nothing. Needed to implement TextWatcher. } - public int getActionModeHeight() { + private Rect mGlobalVisibleRect = new Rect(); + private Point mGlobalVisibleOffset = new Point(); + public int getActionModeGlobalBottom() { if (mActionMode == null) { return 0; } - View parent = (View) mCustomView.getParent(); - return parent != null ? parent.getMeasuredHeight() - : mCustomView.getMeasuredHeight(); + View view = (View) mCustomView.getParent(); + if (view == null) { + view = mCustomView; + } + view.getGlobalVisibleRect(mGlobalVisibleRect, mGlobalVisibleOffset); + return mGlobalVisibleRect.bottom; } } 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..f947f95 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -42,12 +42,14 @@ public class WebSettings { * SINGLE_COLUMN moves all content into one column that is the width of the * view. * NARROW_COLUMNS makes all columns no wider than the screen if possible. - * @deprecated This enum is now obsolete. */ // XXX: These must match LayoutAlgorithm in Settings.h in WebCore. - @Deprecated public enum LayoutAlgorithm { NORMAL, + /** + * @deprecated This algorithm is now obsolete. + */ + @Deprecated SINGLE_COLUMN, NARROW_COLUMNS } @@ -777,7 +779,7 @@ public class WebSettings { public void setDoubleTapZoom(int doubleTapZoom) { if (mDoubleTapZoom != doubleTapZoom) { mDoubleTapZoom = doubleTapZoom; - mWebView.updateDoubleTapZoom(); + mWebView.updateDoubleTapZoom(doubleTapZoom); } } @@ -799,7 +801,7 @@ public class WebSettings { public void setDefaultZoom(ZoomDensity zoom) { if (mDefaultZoom != zoom) { mDefaultZoom = zoom; - mWebView.updateDefaultZoomDensity(zoom.value); + mWebView.adjustDefaultZoomDensity(zoom.value); } } @@ -936,9 +938,7 @@ public class WebSettings { * WebView. * @param l A LayoutAlgorithm enum specifying the algorithm to use. * @see WebSettings.LayoutAlgorithm - * @deprecated This method is now obsolete. */ - @Deprecated public synchronized void setLayoutAlgorithm(LayoutAlgorithm l) { // XXX: This will only be affective if libwebcore was built with // ANDROID_LAYOUT defined. @@ -953,9 +953,7 @@ public class WebSettings { * @return LayoutAlgorithm enum value describing the layout algorithm * being used. * @see WebSettings.LayoutAlgorithm - * @deprecated This method is now obsolete. */ - @Deprecated public synchronized LayoutAlgorithm getLayoutAlgorithm() { return mLayoutAlgorithm; } diff --git a/core/java/android/webkit/WebTextView.java b/core/java/android/webkit/WebTextView.java index 5ee1b8a..f18a396 100644 --- a/core/java/android/webkit/WebTextView.java +++ b/core/java/android/webkit/WebTextView.java @@ -16,14 +16,11 @@ 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.Paint.Style; import android.graphics.Rect; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; @@ -54,18 +51,19 @@ import android.view.ViewGroup; import android.view.inputmethod.EditorInfo; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.widget.AbsoluteLayout; import android.widget.AbsoluteLayout.LayoutParams; import android.widget.AdapterView; 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 @@ -76,6 +74,9 @@ import junit.framework.Assert; static final String LOGTAG = "webtextview"; + private Paint mRingPaint; + private int mRingInset; + private WebView mWebView; private boolean mSingle; private int mWidthSpec; @@ -206,7 +207,13 @@ import junit.framework.Assert; } } }; + float ringWidth = 4f * context.getResources().getDisplayMetrics().density; mReceiver = new MyResultReceiver(mHandler); + mRingPaint = new Paint(); + mRingPaint.setColor(0x6633b5e5); + mRingPaint.setStrokeWidth(ringWidth); + mRingPaint.setStyle(Style.FILL); + mRingInset = (int) ringWidth; } public void setAutoFillable(int queryId) { @@ -216,6 +223,40 @@ import junit.framework.Assert; } @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + if (isFocused()) { + final int ib = getHeight() - mRingInset; + canvas.drawRect(0, 0, getWidth(), mRingInset, mRingPaint); + canvas.drawRect(0, ib, getWidth(), getHeight(), mRingPaint); + canvas.drawRect(0, mRingInset, mRingInset, ib, mRingPaint); + canvas.drawRect(getWidth() - mRingInset, mRingInset, getWidth(), ib, mRingPaint); + } + } + + private void growOrShrink(boolean grow) { + AbsoluteLayout.LayoutParams lp = (AbsoluteLayout.LayoutParams) getLayoutParams(); + if (grow) { + Log.i("webtextview", "grow"); + lp.x -= mRingInset; + lp.y -= mRingInset; + lp.width += 2 * mRingInset; + lp.height += 2 * mRingInset; + setPadding(getPaddingLeft() + mRingInset, getPaddingTop() + mRingInset, + getPaddingRight() + mRingInset, getPaddingBottom() + mRingInset); + } else { + Log.i("webtextview", "shrink"); + lp.x += mRingInset; + lp.y += mRingInset; + lp.width -= 2 * mRingInset; + lp.height -= 2 * mRingInset; + setPadding(getPaddingLeft() - mRingInset, getPaddingTop() - mRingInset, + getPaddingRight() - mRingInset, getPaddingBottom() - mRingInset); + } + setLayoutParams(lp); + } + + @Override public boolean dispatchKeyEvent(KeyEvent event) { if (event.isSystem()) { return super.dispatchKeyEvent(event); @@ -516,6 +557,7 @@ import junit.framework.Assert; } else if (!mInsideRemove) { mWebView.setActive(false); } + growOrShrink(focused); mFromFocusChange = false; } @@ -572,7 +614,6 @@ import junit.framework.Assert; mPreChange.substring(0, mMaxLength).equals(postChange))) { return; } - mPreChange = postChange; if (0 == count) { if (before > 0) { // For this and all changes to the text, update our cache @@ -610,9 +651,9 @@ import junit.framework.Assert; // Prefer sending javascript events, so when adding one character, // don't replace the unchanged text. if (count > 1 && before == count - 1) { - String replaceButOne = s.subSequence(start, + String replaceButOne = mPreChange.subSequence(start, start + before).toString(); - String replacedString = getText().subSequence(start, + String replacedString = s.subSequence(start, start + before).toString(); if (replaceButOne.equals(replacedString)) { // we're just adding one character @@ -621,6 +662,7 @@ import junit.framework.Assert; count = 1; } } + mPreChange = postChange; // Find the last character being replaced. If it can be represented by // events, we will pass them to native so we can see javascript events. // Otherwise, replace the text being changed in the textfield. @@ -926,18 +968,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..35fd945 100644 --- a/core/java/android/webkit/WebView.java +++ b/core/java/android/webkit/WebView.java @@ -119,6 +119,11 @@ import java.util.Vector; import java.util.regex.Matcher; import java.util.regex.Pattern; +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLContext; +import javax.microedition.khronos.egl.EGLDisplay; +import static javax.microedition.khronos.egl.EGL10.*; + /** * <p>A View that displays web pages. This class is the basis upon which you * can roll your own web browser or simply display some online content within your Activity. @@ -641,6 +646,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 +688,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; @@ -909,6 +914,9 @@ public class WebView extends AbsoluteLayout private Rect mScrollingLayerBounds = new Rect(); private boolean mSentAutoScrollMessage = false; + // Temporary hack to work around the context removal upon memory pressure + private static boolean mIncrementEGLContextHack = false; + // used for serializing asynchronously handled touch events. private final TouchEventQueue mTouchEventQueue = new TouchEventQueue(); @@ -1341,9 +1349,13 @@ public class WebView extends AbsoluteLayout } } - /* package */void updateDefaultZoomDensity(int zoomDensity) { + /* package */ void adjustDefaultZoomDensity(int zoomDensity) { final float density = mContext.getResources().getDisplayMetrics().density * 100 / zoomDensity; + updateDefaultZoomDensity(density); + } + + /* package */ void updateDefaultZoomDensity(float density) { mNavSlop = (int) (16 * density); mZoomManager.updateDefaultZoomDensity(density); } @@ -1484,7 +1496,21 @@ public class WebView extends AbsoluteLayout private int getVisibleTitleHeightImpl() { // need to restrict mScrollY due to over scroll return Math.max(getTitleHeight() - Math.max(0, mScrollY), - mFindCallback != null ? mFindCallback.getActionModeHeight() : 0); + getOverlappingActionModeHeight()); + } + + private int mCachedOverlappingActionModeHeight = -1; + + private int getOverlappingActionModeHeight() { + if (mFindCallback == null) { + return 0; + } + if (mCachedOverlappingActionModeHeight < 0) { + getGlobalVisibleRect(mGlobalVisibleRect, mGlobalVisibleOffset); + mCachedOverlappingActionModeHeight = Math.max(0, + mFindCallback.getActionModeGlobalBottom() - mGlobalVisibleRect.top); + } + return mCachedOverlappingActionModeHeight; } /* @@ -2004,15 +2030,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 +2054,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(); @@ -2452,7 +2481,9 @@ public class WebView extends AbsoluteLayout * Set the initial scale for the WebView. 0 means default. If * {@link WebSettings#getUseWideViewPort()} is true, it zooms out all the * way. Otherwise it starts with 100%. If initial scale is greater than 0, - * WebView starts will this value as initial scale. + * WebView starts with this value as initial scale. + * Please note that unlike the scale properties in the viewport meta tag, + * this method doesn't take the screen density into account. * * @param scaleInPercent The initial scale in percent. */ @@ -2844,48 +2875,49 @@ public class WebView extends AbsoluteLayout } // Used to avoid sending many visible rect messages. - private Rect mLastVisibleRectSent; - private Rect mLastGlobalRect; + private Rect mLastVisibleRectSent = new Rect(); + private Rect mLastGlobalRect = new Rect(); + 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.set(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.set(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 +2933,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 +3024,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() { @@ -3079,10 +3111,7 @@ public class WebView extends AbsoluteLayout // Special-case layer scrolling so that we do not trigger normal scroll // updating. if (mTouchMode == TOUCH_DRAG_LAYER_MODE) { - nativeScrollLayer(mScrollingLayer, scrollX, scrollY); - mScrollingLayerRect.left = scrollX; - mScrollingLayerRect.top = scrollY; - invalidate(); + scrollLayerTo(scrollX, scrollY); return; } mInOverScrollMode = false; @@ -3236,6 +3265,26 @@ public class WebView extends AbsoluteLayout if (mHTML5VideoViewProxy != null) { mHTML5VideoViewProxy.pauseAndDispatch(); } + if (mNativeClass != 0) { + nativeSetPauseDrawing(mNativeClass, true); + } + } + } + + @Override + protected void onWindowVisibilityChanged(int visibility) { + super.onWindowVisibilityChanged(visibility); + updateDrawingState(); + } + + void updateDrawingState() { + if (mNativeClass == 0 || mIsPaused) return; + if (getWindowVisibility() != VISIBLE) { + nativeSetPauseDrawing(mNativeClass, true); + } else if (getVisibility() != VISIBLE) { + nativeSetPauseDrawing(mNativeClass, true); + } else { + nativeSetPauseDrawing(mNativeClass, false); } } @@ -3247,6 +3296,9 @@ public class WebView extends AbsoluteLayout if (mIsPaused) { mIsPaused = false; mWebViewCore.sendMessage(EventHub.ON_RESUME); + if (mNativeClass != 0) { + nativeSetPauseDrawing(mNativeClass, false); + } } } @@ -3371,6 +3423,7 @@ public class WebView extends AbsoluteLayout // Could not start the action mode, so end Find on page return false; } + mCachedOverlappingActionModeHeight = -1; mFindCallback = callback; setFindIsUp(true); mFindCallback.setWebView(this); @@ -3488,6 +3541,7 @@ public class WebView extends AbsoluteLayout */ void notifyFindDialogDismissed() { mFindCallback = null; + mCachedOverlappingActionModeHeight = -1; if (mWebViewCore == null) { return; } @@ -3560,12 +3614,9 @@ public class WebView extends AbsoluteLayout mScrollY = y; } else { // Update the layer position instead of WebView. - nativeScrollLayer(mScrollingLayer, x, y); - mScrollingLayerRect.left = x; - mScrollingLayerRect.top = y; + scrollLayerTo(x, y); } abortAnimation(); - mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY); nativeSetIsScrolling(false); if (!mBlockWebkitViewMessages) { WebViewCore.resumePriority(); @@ -3582,6 +3633,17 @@ public class WebView extends AbsoluteLayout } } + private void scrollLayerTo(int x, int y) { + if (x == mScrollingLayerRect.left && y == mScrollingLayerRect.top) { + return; + } + nativeScrollLayer(mScrollingLayer, x, y); + mScrollingLayerRect.left = x; + mScrollingLayerRect.top = y; + onScrollChanged(mScrollX, mScrollY, mScrollX, mScrollY); + invalidate(); + } + private static int computeDuration(int dx, int dy) { int distance = Math.max(Math.abs(dx), Math.abs(dy)); int duration = distance * 1000 / STD_SPEED; @@ -4115,20 +4177,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); } @@ -4185,6 +4233,13 @@ public class WebView extends AbsoluteLayout } if (canvas.isHardwareAccelerated()) { + if (mIncrementEGLContextHack == false) { + mIncrementEGLContextHack = true; + EGL10 egl = (EGL10) EGLContext.getEGL(); + EGLDisplay eglDisplay = egl.eglGetDisplay(EGL_DEFAULT_DISPLAY); + int[] version = new int[2]; + egl.eglInitialize(eglDisplay, version); + } mZoomManager.setHardwareAccelerated(); } @@ -4352,6 +4407,7 @@ public class WebView extends AbsoluteLayout @Override protected void onConfigurationChanged(Configuration newConfig) { + mCachedOverlappingActionModeHeight = -1; if (mSelectingText && mOrientation != newConfig.orientation) { selectionDone(); } @@ -4504,6 +4560,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 +4590,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 +4662,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 +4698,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 +5245,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 +5459,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 +5541,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(); @@ -5584,6 +5649,7 @@ public class WebView extends AbsoluteLayout if (visibility != View.VISIBLE && mZoomManager != null) { mZoomManager.dismissZoomPicker(); } + updateDrawingState(); } /** @@ -5613,16 +5679,13 @@ public class WebView extends AbsoluteLayout if (hasFocus()) { // If our window regained focus, and we have focus, then begin // drawing the cursor ring - mDrawCursorRing = true; + mDrawCursorRing = !inEditingMode(); setFocusControllerActive(true); - if (mNativeClass != 0) { - recordButtons(null, true, false, true); - } } else { + mDrawCursorRing = false; if (!inEditingMode()) { // If our window gained focus, but we do not have it, do not // draw the cursor ring. - mDrawCursorRing = false; setFocusControllerActive(false); } // We do not call recordButtons here because we assume @@ -5643,9 +5706,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(); @@ -5700,10 +5760,7 @@ public class WebView extends AbsoluteLayout // When we regain focus, if we have window focus, resume drawing // the cursor ring if (hasWindowFocus()) { - mDrawCursorRing = true; - if (mNativeClass != 0) { - recordButtons(null, true, false, true); - } + mDrawCursorRing = !inEditingMode(); setFocusControllerActive(true); //} else { // The WebView has gained focus while we do not have @@ -5713,11 +5770,8 @@ public class WebView extends AbsoluteLayout } else { // When we lost focus, unless focus went to the TextView (which is // true if we are in editing mode), stop drawing the cursor ring. + mDrawCursorRing = false; if (!inEditingMode()) { - mDrawCursorRing = false; - if (mNativeClass != 0) { - recordButtons(null, false, false, true); - } setFocusControllerActive(false); } mKeysPressed.clear(); @@ -6005,7 +6059,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 +6342,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 +6367,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 +6884,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 +7395,6 @@ public class WebView extends AbsoluteLayout mLastTouchTime = eventTime; if (!mScroller.isFinished()) { abortAnimation(); - mPrivateHandler.removeMessages(RESUME_WEBCORE_PRIORITY); } mSnapScrollMode = SNAP_NONE; mVelocityTracker = VelocityTracker.obtain(); @@ -8271,12 +8336,8 @@ public class WebView extends AbsoluteLayout if (mScrollingLayer == 0) { pinScrollBy(mAutoScrollX, mAutoScrollY, true, 0); } else { - mScrollingLayerRect.left += mAutoScrollX; - mScrollingLayerRect.top += mAutoScrollY; - nativeScrollLayer(mScrollingLayer, - mScrollingLayerRect.left, - mScrollingLayerRect.top); - invalidate(); + scrollLayerTo(mScrollingLayerRect.left + mAutoScrollX, + mScrollingLayerRect.top + mAutoScrollY); } sendEmptyMessageDelayed( SCROLL_SELECT_TEXT, SELECT_SCROLL_INTERVAL); @@ -8389,6 +8450,9 @@ public class WebView extends AbsoluteLayout setNewPicture(mDelaySetPicture, true); mDelaySetPicture = null; } + if (mIsPaused) { + nativeSetPauseDrawing(mNativeClass, true); + } break; case UPDATE_TEXTFIELD_TEXT_MSG_ID: // Make sure that the textfield is currently focused @@ -8461,10 +8525,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 @@ -8723,27 +8783,6 @@ public class WebView extends AbsoluteLayout isPictureAfterFirstLayout, registerPageSwapCallback); } final Point viewSize = draw.mViewSize; - if (isPictureAfterFirstLayout) { - // Reset the last sent data here since dealing with new page. - mLastWidthSent = 0; - mZoomManager.onFirstLayout(draw); - if (!mDrawHistory) { - // Do not send the scroll event for this particular - // scroll message. Note that a scroll event may - // still be fired if the user scrolls before the - // message can be handled. - mSendScrollEvent = false; - setContentScrollTo(viewState.mScrollX, viewState.mScrollY); - mSendScrollEvent = true; - - // As we are on a new page, remove the WebTextView. This - // is necessary for page loads driven by webkit, and in - // particular when the user was on a password field, so - // the WebTextView was visible. - clearTextEntry(); - } - } - // We update the layout (i.e. request a layout from the // view system) if the last view size that we sent to // WebCore matches the view size of the picture we just @@ -8756,7 +8795,25 @@ public class WebView extends AbsoluteLayout mSendScrollEvent = false; recordNewContentSize(draw.mContentSize.x, draw.mContentSize.y, updateLayout); + + if (isPictureAfterFirstLayout) { + // Reset the last sent data here since dealing with new page. + mLastWidthSent = 0; + mZoomManager.onFirstLayout(draw); + int scrollX = viewState.mShouldStartScrolledRight + ? getContentWidth() : viewState.mScrollX; + int scrollY = viewState.mScrollY; + setContentScrollTo(scrollX, scrollY); + if (!mDrawHistory) { + // As we are on a new page, remove the WebTextView. This + // is necessary for page loads driven by webkit, and in + // particular when the user was on a password field, so + // the WebTextView was visible. + clearTextEntry(); + } + } mSendScrollEvent = true; + if (DebugFlags.WEB_VIEW) { Rect b = draw.mInvalRegion.getBounds(); Log.v(LOGTAG, "NEW_PICTURE_MSG_ID {" + @@ -9412,24 +9469,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 +9565,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,9 +9625,11 @@ 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 */ private static native void nativeOnTrimMemory(int level); + private static native void nativeSetPauseDrawing(int instance, boolean pause); } diff --git a/core/java/android/webkit/WebViewCore.java b/core/java/android/webkit/WebViewCore.java index 1294a28..a97f4dd 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; @@ -1981,6 +1982,7 @@ public final class WebViewCore { int mScrollY; boolean mMobileSite; boolean mIsRestored; + boolean mShouldStartScrolledRight; } static class DrawData { @@ -2254,7 +2256,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 +2273,7 @@ public final class WebViewCore { // reset the scroll position, the restored offset and scales mRestoredX = mRestoredY = 0; + mIsRestored = false; mRestoredScale = mRestoredTextWrapScale = 0; } @@ -2288,6 +2291,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) { @@ -2321,6 +2336,9 @@ public final class WebViewCore { adjust = (float) mContext.getResources().getDisplayMetrics().densityDpi / mViewportDensityDpi; } + if (adjust != mWebView.getDefaultZoomScale()) { + mWebView.updateDefaultZoomDensity(adjust); + } int defaultScale = (int) (adjust * 100); if (mViewportInitialScale > 0) { @@ -2368,6 +2386,7 @@ public final class WebViewCore { viewState.mMobileSite = false; // for non-mobile site, we don't need minPrefWidth, set it as 0 viewState.mScrollX = 0; + viewState.mShouldStartScrolledRight = false; Message.obtain(mWebView.mPrivateHandler, WebView.UPDATE_ZOOM_RANGE, viewState).sendToTarget(); return; @@ -2398,8 +2417,13 @@ public final class WebViewCore { mInitialViewState.mDefaultScale = adjust; mInitialViewState.mScrollX = mRestoredX; mInitialViewState.mScrollY = mRestoredY; + mInitialViewState.mShouldStartScrolledRight = (mRestoredX == 0) + && (mRestoredY == 0) + && (mBrowserFrame != null) + && mBrowserFrame.getShouldStartScrolledRight(); + mInitialViewState.mMobileSite = (0 == mViewportWidth); - if (mRestoredScale > 0) { + if (mIsRestored) { mInitialViewState.mIsRestored = true; mInitialViewState.mViewScale = mRestoredScale; if (mRestoredTextWrapScale > 0) { @@ -2525,13 +2549,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 = true; + mRestoredScale = scale; if (mSettings.getUseWideViewPort()) { - mRestoredTextWrapScale = (textWrapScale <= 0.0) - ? mWebView.getReadingLevelScale() : textWrapScale; + mRestoredTextWrapScale = textWrapScale; } } } diff --git a/core/java/android/webkit/WebViewDatabase.java b/core/java/android/webkit/WebViewDatabase.java index e1392ae..695c154 100644 --- a/core/java/android/webkit/WebViewDatabase.java +++ b/core/java/android/webkit/WebViewDatabase.java @@ -42,7 +42,7 @@ public class WebViewDatabase { // log tag protected static final String LOGTAG = "webviewdatabase"; - private static final int DATABASE_VERSION = 10; + private static final int DATABASE_VERSION = 11; // 2 -> 3 Modified Cache table to allow cache of redirects // 3 -> 4 Added Oma-Downloads table // 4 -> 5 Modified Cache table to support persistent contentLength @@ -52,6 +52,9 @@ public class WebViewDatabase { // 7 -> 8 Move cache to its own db // 8 -> 9 Store both scheme and host when storing passwords // 9 -> 10 Update httpauth table UNIQUE + // 10 -> 11 Drop cookies and cache now managed by the chromium stack, + // and update the form data table to use the new format + // implemented for b/5265606. private static final int CACHE_DATABASE_VERSION = 4; // 1 -> 2 Add expires String // 2 -> 3 Add content-disposition @@ -204,7 +207,9 @@ public class WebViewDatabase { } initDatabase(context); - if (!JniUtil.useChromiumHttpStack()) { + if (JniUtil.useChromiumHttpStack()) { + context.deleteDatabase(CACHE_DATABASE_FILE); + } else { initCacheDatabase(context); } @@ -327,15 +332,59 @@ public class WebViewDatabase { } private static void upgradeDatabase() { + upgradeDatabaseToV10(); + upgradeDatabaseFromV10ToV11(); + // Add future database upgrade functions here, one version at a + // time. + mDatabase.setVersion(DATABASE_VERSION); + } + + private static void upgradeDatabaseFromV10ToV11() { + int oldVersion = mDatabase.getVersion(); + + if (oldVersion >= 11) { + // Nothing to do. + return; + } + + if (JniUtil.useChromiumHttpStack()) { + // Clear out old java stack cookies - this data is now stored in + // a separate database managed by the Chrome stack. + mDatabase.execSQL("DROP TABLE IF EXISTS " + mTableNames[TABLE_COOKIES_ID]); + + // Likewise for the old cache table. + mDatabase.execSQL("DROP TABLE IF EXISTS cache"); + } + + // Update form autocomplete URLs to match new ICS formatting. + Cursor c = mDatabase.query(mTableNames[TABLE_FORMURL_ID], null, null, + null, null, null, null); + while (c.moveToNext()) { + String urlId = Long.toString(c.getLong(c.getColumnIndex(ID_COL))); + String url = c.getString(c.getColumnIndex(FORMURL_URL_COL)); + ContentValues cv = new ContentValues(1); + cv.put(FORMURL_URL_COL, WebTextView.urlForAutoCompleteData(url)); + mDatabase.update(mTableNames[TABLE_FORMURL_ID], cv, ID_COL + "=?", + new String[] { urlId }); + } + c.close(); + } + + private static void upgradeDatabaseToV10() { int oldVersion = mDatabase.getVersion(); + + if (oldVersion >= 10) { + // Nothing to do. + return; + } + if (oldVersion != 0) { Log.i(LOGTAG, "Upgrading database from version " + oldVersion + " to " + DATABASE_VERSION + ", which will destroy old data"); } - boolean justPasswords = 8 == oldVersion && 9 == DATABASE_VERSION; - boolean justAuth = 9 == oldVersion && 10 == DATABASE_VERSION; - if (justAuth) { + + if (9 == oldVersion) { mDatabase.execSQL("DROP TABLE IF EXISTS " + mTableNames[TABLE_HTTPAUTH_ID]); mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID] @@ -348,55 +397,49 @@ public class WebViewDatabase { return; } - if (!justPasswords) { - mDatabase.execSQL("DROP TABLE IF EXISTS " - + mTableNames[TABLE_COOKIES_ID]); - mDatabase.execSQL("DROP TABLE IF EXISTS cache"); - mDatabase.execSQL("DROP TABLE IF EXISTS " - + mTableNames[TABLE_FORMURL_ID]); - mDatabase.execSQL("DROP TABLE IF EXISTS " - + mTableNames[TABLE_FORMDATA_ID]); - mDatabase.execSQL("DROP TABLE IF EXISTS " - + mTableNames[TABLE_HTTPAUTH_ID]); - } + mDatabase.execSQL("DROP TABLE IF EXISTS " + + mTableNames[TABLE_COOKIES_ID]); + mDatabase.execSQL("DROP TABLE IF EXISTS cache"); + mDatabase.execSQL("DROP TABLE IF EXISTS " + + mTableNames[TABLE_FORMURL_ID]); + mDatabase.execSQL("DROP TABLE IF EXISTS " + + mTableNames[TABLE_FORMDATA_ID]); + mDatabase.execSQL("DROP TABLE IF EXISTS " + + mTableNames[TABLE_HTTPAUTH_ID]); mDatabase.execSQL("DROP TABLE IF EXISTS " + mTableNames[TABLE_PASSWORD_ID]); - mDatabase.setVersion(DATABASE_VERSION); - - if (!justPasswords) { - // cookies - mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_COOKIES_ID] - + " (" + ID_COL + " INTEGER PRIMARY KEY, " - + COOKIES_NAME_COL + " TEXT, " + COOKIES_VALUE_COL - + " TEXT, " + COOKIES_DOMAIN_COL + " TEXT, " - + COOKIES_PATH_COL + " TEXT, " + COOKIES_EXPIRES_COL - + " INTEGER, " + COOKIES_SECURE_COL + " INTEGER" + ");"); - mDatabase.execSQL("CREATE INDEX cookiesIndex ON " - + mTableNames[TABLE_COOKIES_ID] + " (path)"); - - // formurl - mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID] - + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL - + " TEXT" + ");"); - - // formdata - mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID] - + " (" + ID_COL + " INTEGER PRIMARY KEY, " - + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL - + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE (" - + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", " - + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);"); + // cookies + mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_COOKIES_ID] + + " (" + ID_COL + " INTEGER PRIMARY KEY, " + + COOKIES_NAME_COL + " TEXT, " + COOKIES_VALUE_COL + + " TEXT, " + COOKIES_DOMAIN_COL + " TEXT, " + + COOKIES_PATH_COL + " TEXT, " + COOKIES_EXPIRES_COL + + " INTEGER, " + COOKIES_SECURE_COL + " INTEGER" + ");"); + mDatabase.execSQL("CREATE INDEX cookiesIndex ON " + + mTableNames[TABLE_COOKIES_ID] + " (path)"); + + // formurl + mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMURL_ID] + + " (" + ID_COL + " INTEGER PRIMARY KEY, " + FORMURL_URL_COL + + " TEXT" + ");"); + + // formdata + mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_FORMDATA_ID] + + " (" + ID_COL + " INTEGER PRIMARY KEY, " + + FORMDATA_URLID_COL + " INTEGER, " + FORMDATA_NAME_COL + + " TEXT, " + FORMDATA_VALUE_COL + " TEXT," + " UNIQUE (" + + FORMDATA_URLID_COL + ", " + FORMDATA_NAME_COL + ", " + + FORMDATA_VALUE_COL + ") ON CONFLICT IGNORE);"); - // httpauth - mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID] - + " (" + ID_COL + " INTEGER PRIMARY KEY, " - + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL - + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, " - + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE (" - + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL - + ") ON CONFLICT REPLACE);"); - } + // httpauth + mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_HTTPAUTH_ID] + + " (" + ID_COL + " INTEGER PRIMARY KEY, " + + HTTPAUTH_HOST_COL + " TEXT, " + HTTPAUTH_REALM_COL + + " TEXT, " + HTTPAUTH_USERNAME_COL + " TEXT, " + + HTTPAUTH_PASSWORD_COL + " TEXT," + " UNIQUE (" + + HTTPAUTH_HOST_COL + ", " + HTTPAUTH_REALM_COL + + ") ON CONFLICT REPLACE);"); // passwords mDatabase.execSQL("CREATE TABLE " + mTableNames[TABLE_PASSWORD_ID] + " (" + ID_COL + " INTEGER PRIMARY KEY, " @@ -411,7 +454,7 @@ public class WebViewDatabase { if (oldVersion != 0) { Log.i(LOGTAG, "Upgrading cache database from version " + oldVersion + " to " - + DATABASE_VERSION + ", which will destroy all old data"); + + CACHE_DATABASE_VERSION + ", which will destroy all old data"); } mCacheDatabase.execSQL("DROP TABLE IF EXISTS cache"); mCacheDatabase.setVersion(CACHE_DATABASE_VERSION); @@ -1150,7 +1193,7 @@ public class WebViewDatabase { cursor = mDatabase.query(mTableNames[TABLE_FORMURL_ID], ID_PROJECTION, urlSelection, new String[] { url }, null, null, null); - if (cursor.moveToFirst()) { + while (cursor.moveToNext()) { long urlid = cursor.getLong(cursor.getColumnIndex(ID_COL)); Cursor dataCursor = null; try { diff --git a/core/java/android/webkit/ZoomManager.java b/core/java/android/webkit/ZoomManager.java index 9151fdd..84d00c9 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() { @@ -351,8 +350,6 @@ class ZoomManager { public final void setInitialScaleInPercent(int scaleInPercent) { mInitialScale = scaleInPercent * 0.01f; - mActualScale = mInitialScale > 0 ? mInitialScale : mDefaultScale; - mInvActualScale = 1 / mActualScale; } 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/EdgeEffect.java b/core/java/android/widget/EdgeEffect.java index fd2abc2..326587e 100644 --- a/core/java/android/widget/EdgeEffect.java +++ b/core/java/android/widget/EdgeEffect.java @@ -129,7 +129,7 @@ public class EdgeEffect { mEdge = res.getDrawable(R.drawable.overscroll_edge); mGlow = res.getDrawable(R.drawable.overscroll_glow); - mMinWidth = (int) (context.getResources().getDisplayMetrics().density * MIN_WIDTH + 0.5f); + mMinWidth = (int) (res.getDisplayMetrics().density * MIN_WIDTH + 0.5f); mInterpolator = new DecelerateInterpolator(); } 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/ImageView.java b/core/java/android/widget/ImageView.java index b24dd69..73e1273 100644 --- a/core/java/android/widget/ImageView.java +++ b/core/java/android/widget/ImageView.java @@ -1035,4 +1035,29 @@ public class ImageView extends View { mDrawable.setAlpha(mAlpha * mViewAlphaScale >> 8); } } + + @RemotableViewMethod + @Override + public void setVisibility(int visibility) { + super.setVisibility(visibility); + if (mDrawable != null) { + mDrawable.setVisible(visibility == VISIBLE, false); + } + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + if (mDrawable != null) { + mDrawable.setVisible(getVisibility() == VISIBLE, false); + } + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + if (mDrawable != null) { + mDrawable.setVisible(false, false); + } + } } 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..ebb2604 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.getTextServicesLocale()); + + 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.getTextServicesLocale(); + 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..185cfa9 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -24,6 +24,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.content.res.ColorStateList; +import android.content.res.CompatibilityInfo; import android.content.res.Resources; import android.content.res.TypedArray; import android.content.res.XmlResourceParser; @@ -36,7 +37,6 @@ import android.graphics.RectF; import android.graphics.Typeface; import android.graphics.drawable.Drawable; import android.inputmethodservice.ExtractEditText; -import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.Message; @@ -101,7 +101,6 @@ import android.util.Log; import android.util.TypedValue; import android.view.ActionMode; import android.view.ActionMode.Callback; -import android.view.ContextMenu; import android.view.DragEvent; import android.view.Gravity; import android.view.HapticFeedbackConstants; @@ -132,6 +131,8 @@ import android.view.inputmethod.ExtractedText; import android.view.inputmethod.ExtractedTextRequest; import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; +import android.view.textservice.SpellCheckerSubtype; +import android.view.textservice.TextServicesManager; 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; @@ -445,18 +449,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener super(context, attrs, defStyle); mText = ""; + final Resources res = getResources(); + final CompatibilityInfo compat = res.getCompatibilityInfo(); + mTextPaint = new TextPaint(Paint.ANTI_ALIAS_FLAG); - mTextPaint.density = getResources().getDisplayMetrics().density; - mTextPaint.setCompatibilityScaling( - getResources().getCompatibilityInfo().applicationScale); + mTextPaint.density = res.getDisplayMetrics().density; + mTextPaint.setCompatibilityScaling(compat.applicationScale); // If we get the paint from the skin, we should set it to left, since // the layout always wants it to be left. // mTextPaint.setTextAlign(Paint.Align.LEFT); mHighlightPaint = new Paint(Paint.ANTI_ALIAS_FLAG); - mHighlightPaint.setCompatibilityScaling( - getResources().getCompatibilityInfo().applicationScale); + mHighlightPaint.setCompatibilityScaling(compat.applicationScale); mMovement = getDefaultMovementMethod(); mTransformation = null; @@ -605,6 +610,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 +2379,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 +2976,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 +2996,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)) { @@ -3747,7 +3785,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return; } } - + // This is the handling for some default action. // Note that for backwards compatibility we don't do this // default handling if explicit ime options have not been given, @@ -5303,7 +5341,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener switch (keyCode) { case KeyEvent.KEYCODE_ENTER: - mEnterKeyIsDown = true; if (event.hasNoModifiers()) { // When mInputContentType is set, we know that we are // running in a "modern" cupcake environment, so don't need @@ -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; @@ -5335,7 +5372,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener break; case KeyEvent.KEYCODE_DPAD_CENTER: - mDPadCenterIsDown = true; if (event.hasNoModifiers()) { if (shouldAdvanceFocusOnEnter()) { return 0; @@ -5449,7 +5485,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener switch (keyCode) { case KeyEvent.KEYCODE_DPAD_CENTER: - mDPadCenterIsDown = false; if (event.hasNoModifiers()) { /* * If there is a click listener, just call through to @@ -5460,12 +5495,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); } } @@ -5474,7 +5509,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener return super.onKeyUp(keyCode, event); case KeyEvent.KEYCODE_ENTER: - mEnterKeyIsDown = false; if (event.hasNoModifiers()) { if (mInputContentType != null && mInputContentType.onEditorActionListener != null @@ -5498,7 +5532,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) { @@ -5560,6 +5594,8 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener outAttrs.extras = mInputContentType.extras; } else { outAttrs.imeOptions = EditorInfo.IME_NULL; + // May not be defined otherwise and needed by onEditorAction + mInputContentType = new InputContentType(); } if (focusSearch(FOCUS_DOWN) != null) { outAttrs.imeOptions |= EditorInfo.IME_FLAG_NAVIGATE_NEXT; @@ -8207,6 +8243,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } hideControllers(); + if (mSuggestionsPopupWindow != null) { + mSuggestionsPopupWindow.onParentLostFocus(); + } } startStopMarquee(hasWindowFocus); @@ -8304,7 +8343,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); } @@ -8315,10 +8354,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener // When the cursor moves, the word that was typed may need spell check mSpellChecker.onSelectionChanged(); } - if (isCursorInsideEasyCorrectionSpan()) { - showSuggestions(); - } else if (hasInsertionController()) { - getInsertionController().show(); + if (!extractedTextModeWillBeStarted()) { + if (isCursorInsideEasyCorrectionSpan()) { + showSuggestions(); + } else if (hasInsertionController()) { + getInsertionController().show(); + } } } @@ -8840,15 +8881,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 +8902,40 @@ 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 locale that should be used for a word iterator and a spell checker + * in this TextView, based on the current spell checker settings, + * the current IME's locale, or the system default locale. + * @hide + */ + public Locale getTextServicesLocale() { + Locale locale = Locale.getDefault(); + final TextServicesManager textServicesManager = (TextServicesManager) + mContext.getSystemService(Context.TEXT_SERVICES_MANAGER_SERVICE); + final SpellCheckerSubtype subtype = textServicesManager.getCurrentSpellCheckerSubtype(true); + if (subtype != null) { + locale = new Locale(subtype.getLocale()); + } + 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(getTextServicesLocale()); + } + return mWordIterator; + } + private long getCharRange(int offset) { final int textLength = mText.length(); if (offset + 1 < textLength) { @@ -8896,17 +8969,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } private long getLastTouchOffsets() { - int minOffset, maxOffset; - - if (mContextMenuTriggeredByKey) { - minOffset = getSelectionStart(); - maxOffset = getSelectionEnd(); - } else { - SelectionModifierCursorController selectionController = getSelectionController(); - minOffset = selectionController.getMinTouchOffset(); - maxOffset = selectionController.getMaxTouchOffset(); - } - + SelectionModifierCursorController selectionController = getSelectionController(); + final int minOffset = selectionController.getMinTouchOffset(); + final int maxOffset = selectionController.getMaxTouchOffset(); return packRangeInLong(minOffset, maxOffset); } @@ -8984,51 +9049,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,21 +9063,11 @@ 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) { - return onTextContextMenuItem(item.getItemId()); - } - } /** * 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 +9084,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 +9530,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 +9586,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 +9601,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 +9697,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener updateSuggestions(); mCursorWasVisibleBeforeSuggestions = mCursorVisible; setCursorVisible(false); + mIsShowingUp = true; super.show(); } @@ -9936,6 +9919,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 +9935,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())) { @@ -10108,27 +10097,35 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } } - final InputMethodManager imm = InputMethodManager.peekInstance(); - boolean extractedTextModeWillBeStartedFullScreen = !(this instanceof ExtractEditText) && - imm != null && imm.isFullscreenMode(); + boolean willExtract = extractedTextModeWillBeStarted(); // Do not start the action mode when extracted text will show up full screen, thus // immediately hiding the newly created action bar, which would be visually distracting. - if (!extractedTextModeWillBeStartedFullScreen) { + if (!willExtract) { ActionMode.Callback actionModeCallback = new SelectionActionModeCallback(); mSelectionActionMode = startActionMode(actionModeCallback); } - final boolean selectionStarted = mSelectionActionMode != null || - extractedTextModeWillBeStartedFullScreen; - if (selectionStarted && !mTextIsSelectable && imm != null) { + final boolean selectionStarted = mSelectionActionMode != null || willExtract; + if (selectionStarted && !mTextIsSelectable && mSoftInputShownOnFocus) { // Show the IME to be able to replace text, except when selecting non editable text. - imm.showSoftInput(this, 0, null); + final InputMethodManager imm = InputMethodManager.peekInstance(); + if (imm != null) { + imm.showSoftInput(this, 0, null); + } } return selectionStarted; } + private boolean extractedTextModeWillBeStarted() { + if (!(this instanceof ExtractEditText)) { + final InputMethodManager imm = InputMethodManager.peekInstance(); + return imm != null && imm.isFullscreenMode(); + } + return false; + } + private void stopSelectionActionMode() { if (mSelectionActionMode != null) { // This will hide the mSelectionModifierCursorController @@ -11121,6 +11118,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(); } @@ -11462,12 +11463,6 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private boolean mSelectionControllerEnabled; private boolean mInBatchEditControllers; - // These are needed to desambiguate a long click. If the long click comes from ones of these, we - // select from the current cursor position. Otherwise, select from long pressed position. - private boolean mDPadCenterIsDown = false; - private boolean mEnterKeyIsDown = false; - private boolean mContextMenuTriggeredByKey = false; - private boolean mSelectAllOnFocus = false; private int mGravity = Gravity.TOP | Gravity.START; 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/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/ActionBarView.java b/core/java/com/android/internal/widget/ActionBarView.java index e131242..b689f53 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); @@ -536,7 +537,7 @@ public class ActionBarView extends AbsActionBarView { if ((flagsChanged & DISPLAY_RELAYOUT_MASK) != 0) { final boolean showHome = (options & ActionBar.DISPLAY_SHOW_HOME) != 0; - final int vis = showHome ? VISIBLE : GONE; + final int vis = showHome && mExpandedActionView == null ? VISIBLE : GONE; mHomeLayout.setVisibility(vis); if ((flagsChanged & ActionBar.DISPLAY_HOME_AS_UP) != 0) { diff --git a/core/java/com/android/internal/widget/DigitalClock.java b/core/java/com/android/internal/widget/DigitalClock.java index 18a4794..daefc9a 100644 --- a/core/java/com/android/internal/widget/DigitalClock.java +++ b/core/java/com/android/internal/widget/DigitalClock.java @@ -106,7 +106,8 @@ public class DigitalClock extends RelativeLayout { private String mAmString, mPmString; AmPm(View parent, Typeface tf) { - mAmPmTextView = (TextView) parent.findViewById(R.id.am_pm); + // No longer used, uncomment if we decide to use AM/PM indicator again + // mAmPmTextView = (TextView) parent.findViewById(R.id.am_pm); if (mAmPmTextView != null && tf != null) { mAmPmTextView.setTypeface(tf); } @@ -168,6 +169,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 17b8acf..89f9d4e 100644 --- a/core/java/com/android/internal/widget/LockPatternUtils.java +++ b/core/java/com/android/internal/widget/LockPatternUtils.java @@ -25,14 +25,11 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; -import android.hardware.Camera; -import android.hardware.Camera.CameraInfo; import android.os.FileObserver; import android.os.IBinder; import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; -import android.os.SystemProperties; import android.os.storage.IMountService; import android.provider.Settings; import android.security.KeyStore; @@ -41,7 +38,6 @@ import android.text.TextUtils; import android.util.Log; import android.view.View; import android.widget.Button; -import android.widget.TextView; import java.io.File; import java.io.FileNotFoundException; @@ -968,6 +964,11 @@ public class LockPatternUtils { com.android.internal.R.bool.config_enable_puk_unlock_screen); } + public boolean isEmergencyCallEnabledWhileSimLocked() { + return mContext.getResources().getBoolean( + com.android.internal.R.bool.config_enable_emergency_call_while_sim_locked); + } + /** * @return A formatted string of the next alarm (for showing on the lock screen), * or null if there is no next alarm. @@ -1031,12 +1032,10 @@ public class LockPatternUtils { * {@link TelephonyManager#CALL_STATE_IDLE} * {@link TelephonyManager#CALL_STATE_RINGING} * {@link TelephonyManager#CALL_STATE_OFFHOOK} - * @param showIfCapable indicates whether the button should be shown if emergency calls are - * possible on the device + * @param shown indicates whether the given screen wants the emergency button to show at all */ - public void updateEmergencyCallButtonState(Button button, int phoneState, - boolean showIfCapable) { - if (isEmergencyCallCapable() && showIfCapable) { + public void updateEmergencyCallButtonState(Button button, int phoneState, boolean shown) { + if (isEmergencyCallCapable() && shown) { button.setVisibility(View.VISIBLE); } else { button.setVisibility(View.GONE); diff --git a/core/java/com/android/internal/widget/LockPatternView.java b/core/java/com/android/internal/widget/LockPatternView.java index a2fc6e2..0d9cf9a 100644 --- a/core/java/com/android/internal/widget/LockPatternView.java +++ b/core/java/com/android/internal/widget/LockPatternView.java @@ -463,7 +463,7 @@ public class LockPatternView extends View { result = desired; break; case MeasureSpec.AT_MOST: - result = Math.min(specSize, desired); + result = Math.max(specSize, desired); break; case MeasureSpec.EXACTLY: default: 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) { |
