diff options
author | Jean-Baptiste Queru <jbq@google.com> | 2009-07-29 14:25:07 -0700 |
---|---|---|
committer | Jean-Baptiste Queru <jbq@google.com> | 2009-07-29 14:25:07 -0700 |
commit | a8675f67e33bc7337d148358783b0fd138b501ff (patch) | |
tree | 71fb9d10330ef9161b3ead71d01074b3ef9e53ba /core/java/android | |
parent | cf4550c3198d6b3d92cdc52707fe70d7cc0caa9f (diff) | |
download | frameworks_base-a8675f67e33bc7337d148358783b0fd138b501ff.zip frameworks_base-a8675f67e33bc7337d148358783b0fd138b501ff.tar.gz frameworks_base-a8675f67e33bc7337d148358783b0fd138b501ff.tar.bz2 |
donut snapshot
Diffstat (limited to 'core/java/android')
73 files changed, 3423 insertions, 1492 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index ca9632a..4ac3b9e 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -606,13 +606,13 @@ public class Activity extends ContextThemeWrapper private static final String SAVED_DIALOG_IDS_KEY = "android:savedDialogIds"; private static final String SAVED_DIALOGS_TAG = "android:savedDialogs"; private static final String SAVED_DIALOG_KEY_PREFIX = "android:dialog_"; - private static final String SAVED_SEARCH_DIALOG_KEY = "android:search_dialog"; private SparseArray<Dialog> mManagedDialogs; // set by the thread after the constructor and before onCreate(Bundle savedInstanceState) is called. private Instrumentation mInstrumentation; private IBinder mToken; + private int mIdent; /*package*/ String mEmbeddedID; private Application mApplication; /*package*/ Intent mIntent; @@ -630,7 +630,6 @@ public class Activity extends ContextThemeWrapper /*package*/ int mConfigChangeFlags; /*package*/ Configuration mCurrentConfig; private SearchManager mSearchManager; - private Bundle mSearchDialogState = null; private Window mWindow; @@ -791,9 +790,6 @@ public class Activity extends ContextThemeWrapper protected void onCreate(Bundle savedInstanceState) { mVisibleFromClient = mWindow.getWindowStyle().getBoolean( com.android.internal.R.styleable.Window_windowNoDisplay, true); - // uses super.getSystemService() since this.getSystemService() looks at the - // mSearchManager field. - mSearchManager = (SearchManager) super.getSystemService(Context.SEARCH_SERVICE); mCalled = true; } @@ -808,13 +804,6 @@ public class Activity extends ContextThemeWrapper final void performRestoreInstanceState(Bundle savedInstanceState) { onRestoreInstanceState(savedInstanceState); restoreManagedDialogs(savedInstanceState); - - // Also restore the state of a search dialog (if any) - // TODO more generic than just this manager - Bundle searchState = savedInstanceState.getBundle(SAVED_SEARCH_DIALOG_KEY); - if (searchState != null) { - mSearchManager.restoreSearchDialog(searchState); - } } /** @@ -866,7 +855,7 @@ public class Activity extends ContextThemeWrapper if (dialogState != null) { // Calling onRestoreInstanceState() below will invoke dispatchOnCreate // so tell createDialog() not to do it, otherwise we get an exception - final Dialog dialog = createDialog(dialogId, false); + final Dialog dialog = createDialog(dialogId, dialogState); mManagedDialogs.put(dialogId, dialog); onPrepareDialog(dialogId, dialog); dialog.onRestoreInstanceState(dialogState); @@ -874,13 +863,13 @@ public class Activity extends ContextThemeWrapper } } - private Dialog createDialog(Integer dialogId, boolean dispatchOnCreate) { + private Dialog createDialog(Integer dialogId, Bundle state) { final Dialog dialog = onCreateDialog(dialogId); if (dialog == null) { throw new IllegalArgumentException("Activity#onCreateDialog did " + "not create a dialog for id " + dialogId); } - if (dispatchOnCreate) dialog.dispatchOnCreate(null); + dialog.dispatchOnCreate(state); return dialog; } @@ -1030,14 +1019,6 @@ public class Activity extends ContextThemeWrapper final void performSaveInstanceState(Bundle outState) { onSaveInstanceState(outState); saveManagedDialogs(outState); - - // Also save the state of a search dialog (if any) - // TODO more generic than just this manager - // onPause() should always be called before this method, so mSearchManagerState - // should be up to date. - if (mSearchDialogState != null) { - outState.putBundle(SAVED_SEARCH_DIALOG_KEY, mSearchDialogState); - } } /** @@ -1317,10 +1298,6 @@ public class Activity extends ContextThemeWrapper c.mCursor.close(); } } - - // Clear any search state saved in performPause(). If the state may be needed in the - // future, it will have been saved by performSaveInstanceState() - mSearchDialogState = null; } /** @@ -1341,11 +1318,7 @@ public class Activity extends ContextThemeWrapper */ public void onConfigurationChanged(Configuration newConfig) { mCalled = true; - - // also update search dialog if showing - // TODO more generic than just this manager - mSearchManager.onConfigurationChanged(newConfig); - + if (mWindow != null) { // Pass the configuration changed event to the window mWindow.onConfigurationChanged(newConfig); @@ -2432,7 +2405,7 @@ public class Activity extends ContextThemeWrapper } Dialog dialog = mManagedDialogs.get(id); if (dialog == null) { - dialog = createDialog(id, true); + dialog = createDialog(id, null); mManagedDialogs.put(id, dialog); } @@ -2556,6 +2529,7 @@ public class Activity extends ContextThemeWrapper */ public void startSearch(String initialQuery, boolean selectInitialQuery, Bundle appSearchData, boolean globalSearch) { + ensureSearchManager(); mSearchManager.startSearch(initialQuery, selectInitialQuery, getComponentName(), appSearchData, globalSearch); } @@ -3266,6 +3240,24 @@ public class Activity extends ContextThemeWrapper return getSharedPreferences(getLocalClassName(), mode); } + private void ensureSearchManager() { + if (mSearchManager != null) { + return; + } + + // uses super.getSystemService() since this.getSystemService() looks at the + // mSearchManager field. + mSearchManager = (SearchManager) super.getSystemService(Context.SEARCH_SERVICE); + int ident = mIdent; + if (ident == 0) { + if (mParent != null) ident = mParent.mIdent; + if (ident == 0) { + throw new IllegalArgumentException("no ident"); + } + } + mSearchManager.setIdent(ident); + } + @Override public Object getSystemService(String name) { if (getBaseContext() == null) { @@ -3276,6 +3268,7 @@ public class Activity extends ContextThemeWrapper if (WINDOW_SERVICE.equals(name)) { return mWindowManager; } else if (SEARCH_SERVICE.equals(name)) { + ensureSearchManager(); return mSearchManager; } return super.getSystemService(name); @@ -3475,14 +3468,17 @@ public class Activity extends ContextThemeWrapper Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, Object lastNonConfigurationInstance, Configuration config) { - attach(context, aThread, instr, token, application, intent, info, title, parent, id, + attach(context, aThread, instr, token, 0, application, intent, info, title, parent, id, lastNonConfigurationInstance, null, config); } - final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, - Application application, Intent intent, ActivityInfo info, CharSequence title, - Activity parent, String id, Object lastNonConfigurationInstance, - HashMap<String,Object> lastNonConfigurationChildInstances, Configuration config) { + final void attach(Context context, ActivityThread aThread, + Instrumentation instr, IBinder token, int ident, + Application application, Intent intent, ActivityInfo info, + CharSequence title, Activity parent, String id, + Object lastNonConfigurationInstance, + HashMap<String,Object> lastNonConfigurationChildInstances, + Configuration config) { attachBaseContext(context); mWindow = PolicyManager.makeNewWindow(this); @@ -3495,6 +3491,7 @@ public class Activity extends ContextThemeWrapper mMainThread = aThread; mInstrumentation = instr; mToken = token; + mIdent = ident; mApplication = application; mIntent = intent; mComponent = intent.getComponent(); @@ -3575,21 +3572,10 @@ public class Activity extends ContextThemeWrapper "Activity " + mComponent.toShortString() + " did not call through to super.onPostResume()"); } - - // restore search dialog, if any - if (mSearchDialogState != null) { - mSearchManager.restoreSearchDialog(mSearchDialogState); - } - mSearchDialogState = null; } final void performPause() { onPause(); - - // save search dialog state if the search dialog is open, - // and then dismiss the search dialog - mSearchDialogState = mSearchManager.saveSearchDialog(); - mSearchManager.stopSearch(); } final void performUserLeaving() { diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index dfa8139..ba6cc32 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -881,11 +881,11 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } - case SET_ACTIVITY_WATCHER_TRANSACTION: { + case SET_ACTIVITY_CONTROLLER_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); - IActivityWatcher watcher = IActivityWatcher.Stub.asInterface( + IActivityController watcher = IActivityController.Stub.asInterface( data.readStrongBinder()); - setActivityWatcher(watcher); + setActivityController(watcher); return true; } @@ -1052,6 +1052,39 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeNoException(); return true; } + + case REGISTER_ACTIVITY_WATCHER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IActivityWatcher watcher = IActivityWatcher.Stub.asInterface( + data.readStrongBinder()); + registerActivityWatcher(watcher); + return true; + } + + case UNREGISTER_ACTIVITY_WATCHER_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IActivityWatcher watcher = IActivityWatcher.Stub.asInterface( + data.readStrongBinder()); + unregisterActivityWatcher(watcher); + return true; + } + + case START_ACTIVITY_IN_PACKAGE_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + int uid = data.readInt(); + Intent intent = Intent.CREATOR.createFromParcel(data); + String resolvedType = data.readString(); + IBinder resultTo = data.readStrongBinder(); + String resultWho = data.readString(); + int requestCode = data.readInt(); + boolean onlyIfNeeded = data.readInt() != 0; + int result = startActivityInPackage(uid, intent, resolvedType, + resultTo, resultWho, requestCode, onlyIfNeeded); + reply.writeNoException(); + reply.writeInt(result); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -2105,13 +2138,13 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } - public void setActivityWatcher(IActivityWatcher watcher) throws RemoteException + public void setActivityController(IActivityController watcher) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(watcher != null ? watcher.asBinder() : null); - mRemote.transact(SET_ACTIVITY_WATCHER_TRANSACTION, data, reply, 0); + mRemote.transact(SET_ACTIVITY_CONTROLLER_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); reply.recycle(); @@ -2290,5 +2323,51 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); } + public void registerActivityWatcher(IActivityWatcher watcher) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(watcher != null ? watcher.asBinder() : null); + mRemote.transact(REGISTER_ACTIVITY_WATCHER_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public void unregisterActivityWatcher(IActivityWatcher watcher) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(watcher != null ? watcher.asBinder() : null); + mRemote.transact(UNREGISTER_ACTIVITY_WATCHER_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + + public int startActivityInPackage(int uid, + Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, boolean onlyIfNeeded) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeInt(uid); + intent.writeToParcel(data, 0); + data.writeString(resolvedType); + data.writeStrongBinder(resultTo); + data.writeString(resultWho); + data.writeInt(requestCode); + data.writeInt(onlyIfNeeded ? 1 : 0); + mRemote.transact(START_ACTIVITY_IN_PACKAGE_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + reply.recycle(); + data.recycle(); + return result; + } + private IBinder mRemote; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 5ee29ac..76b47f1 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -32,8 +32,8 @@ import android.content.pm.InstrumentationInfo; import android.content.pm.PackageManager; import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; -import android.content.pm.PackageParser.Component; import android.content.res.AssetManager; +import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.database.sqlite.SQLiteDatabase; @@ -166,43 +166,52 @@ public final class ActivityThread { return metrics; } - Resources getTopLevelResources(String appDir, PackageInfo pkgInfo) { + /** + * Creates the top level Resources for applications with the given compatibility info. + * + * @param resDir the resource directory. + * @param compInfo the compability info. It will use the default compatibility info when it's + * null. + */ + Resources getTopLevelResources(String resDir, CompatibilityInfo compInfo) { synchronized (mPackages) { - //Log.w(TAG, "getTopLevelResources: " + appDir); - WeakReference<Resources> wr = mActiveResources.get(appDir); + // Resources is app scale dependent. + ResourcesKey key = new ResourcesKey(resDir, compInfo.applicationScale); + //Log.w(TAG, "getTopLevelResources: " + resDir); + WeakReference<Resources> wr = mActiveResources.get(key); Resources r = wr != null ? wr.get() : null; if (r != null && r.getAssets().isUpToDate()) { - //Log.w(TAG, "Returning cached resources " + r + " " + appDir); + //Log.w(TAG, "Returning cached resources " + r + " " + resDir); return r; } //if (r != null) { // Log.w(TAG, "Throwing away out-of-date resources!!!! " - // + r + " " + appDir); + // + r + " " + resDir); //} AssetManager assets = new AssetManager(); - if (assets.addAssetPath(appDir) == 0) { + if (assets.addAssetPath(resDir) == 0) { return null; } - ApplicationInfo appInfo; - try { - appInfo = getPackageManager().getApplicationInfo( - pkgInfo.getPackageName(), - PackageManager.GET_SUPPORTS_DENSITIES); - } catch (RemoteException e) { - throw new AssertionError(e); - } - //Log.i(TAG, "Resource:" + appDir + ", display metrics=" + metrics); + + //Log.i(TAG, "Resource: key=" + key + ", display metrics=" + metrics); DisplayMetrics metrics = getDisplayMetricsLocked(false); - r = new Resources(assets, metrics, getConfiguration(), appInfo); + r = new Resources(assets, metrics, getConfiguration(), compInfo); //Log.i(TAG, "Created app resources " + r + ": " + r.getConfiguration()); // XXX need to remove entries when weak references go away - mActiveResources.put(appDir, new WeakReference<Resources>(r)); + mActiveResources.put(key, new WeakReference<Resources>(r)); return r; } } + /** + * Creates the top level resources for the given package. + */ + Resources getTopLevelResources(String resDir, PackageInfo pkgInfo) { + return getTopLevelResources(resDir, pkgInfo.mCompatibilityInfo); + } + final Handler getHandler() { return mH; } @@ -223,6 +232,7 @@ public final class ActivityThread { private Resources mResources; private ClassLoader mClassLoader; private Application mApplication; + private CompatibilityInfo mCompatibilityInfo; private final HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>> mReceivers = new HashMap<Context, HashMap<BroadcastReceiver, ReceiverDispatcher>>(); @@ -250,6 +260,7 @@ public final class ActivityThread { mBaseClassLoader = baseLoader; mSecurityViolation = securityViolation; mIncludeCode = includeCode; + mCompatibilityInfo = new CompatibilityInfo(aInfo); if (mAppDir == null) { if (mSystemContext == null) { @@ -283,6 +294,7 @@ public final class ActivityThread { mIncludeCode = true; mClassLoader = systemContext.getClassLoader(); mResources = systemContext.getResources(); + mCompatibilityInfo = new CompatibilityInfo(mApplicationInfo); } public String getPackageName() { @@ -1077,6 +1089,7 @@ public final class ActivityThread { private static final class ActivityRecord { IBinder token; + int ident; Intent intent; Bundle state; Activity activity; @@ -1286,12 +1299,13 @@ public final class ActivityThread { // we use token to identify this activity without having to send the // activity itself back to the activity manager. (matters more with ipc) - public final void scheduleLaunchActivity(Intent intent, IBinder token, + public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Bundle state, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, boolean notResumed, boolean isForward) { ActivityRecord r = new ActivityRecord(); r.token = token; + r.ident = ident; r.intent = intent; r.activityInfo = info; r.state = state; @@ -1894,6 +1908,32 @@ public final class ActivityThread { } } + private final static class ResourcesKey { + final private String mResDir; + final private float mScale; + final private int mHash; + + ResourcesKey(String resDir, float scale) { + mResDir = resDir; + mScale = scale; + mHash = mResDir.hashCode() << 2 + (int) (mScale * 2); + } + + @Override + public int hashCode() { + return mHash; + } + + @Override + public boolean equals(Object obj) { + if (!(obj instanceof ResourcesKey)) { + return false; + } + ResourcesKey peer = (ResourcesKey) obj; + return mResDir.equals(peer.mResDir) && mScale == peer.mScale; + } + } + static IPackageManager sPackageManager; final ApplicationThread mAppThread = new ApplicationThread(); @@ -1939,8 +1979,8 @@ public final class ActivityThread { = new HashMap<String, WeakReference<PackageInfo>>(); Display mDisplay = null; DisplayMetrics mDisplayMetrics = null; - HashMap<String, WeakReference<Resources> > mActiveResources - = new HashMap<String, WeakReference<Resources> >(); + HashMap<ResourcesKey, WeakReference<Resources> > mActiveResources + = new HashMap<ResourcesKey, WeakReference<Resources> >(); // The lock of mProviderMap protects the following variables. final HashMap<String, ProviderRecord> mProviderMap @@ -2158,21 +2198,11 @@ public final class ActivityThread { } public final Activity startActivityNow(Activity parent, String id, - Intent intent, IBinder token, Bundle state) { - ActivityInfo aInfo = resolveActivityInfo(intent); - return startActivityNow(parent, id, intent, aInfo, token, state); - } - - public final Activity startActivityNow(Activity parent, String id, - Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state) { - return startActivityNow(parent, id, intent, activityInfo, token, state, null); - } - - public final Activity startActivityNow(Activity parent, String id, Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state, Object lastNonConfigurationInstance) { ActivityRecord r = new ActivityRecord(); r.token = token; + r.ident = 0; r.intent = intent; r.state = state; r.parent = parent; @@ -2296,10 +2326,10 @@ public final class ActivityThread { appContext.setOuterContext(activity); CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mConfiguration); - activity.attach(appContext, this, getInstrumentation(), r.token, app, - r.intent, r.activityInfo, title, r.parent, r.embeddedID, - r.lastNonConfigurationInstance, r.lastNonConfigurationChildInstances, - config); + activity.attach(appContext, this, getInstrumentation(), r.token, + r.ident, app, r.intent, r.activityInfo, title, r.parent, + r.embeddedID, r.lastNonConfigurationInstance, + r.lastNonConfigurationChildInstances, config); if (customIntent != null) { activity.mIntent = customIntent; @@ -2545,32 +2575,39 @@ public final class ActivityThread { classname = "android.app.FullBackupAgent"; } try { - java.lang.ClassLoader cl = packageInfo.getClassLoader(); - agent = (BackupAgent) cl.loadClass(data.appInfo.backupAgentName).newInstance(); - } catch (Exception e) { - throw new RuntimeException("Unable to instantiate backup agent " - + data.appInfo.backupAgentName + ": " + e.toString(), e); - } - - // set up the agent's context - try { - if (DEBUG_BACKUP) Log.v(TAG, "Initializing BackupAgent " - + data.appInfo.backupAgentName); - - ApplicationContext context = new ApplicationContext(); - context.init(packageInfo, null, this); - context.setOuterContext(agent); - agent.attach(context); - agent.onCreate(); + IBinder binder = null; + try { + java.lang.ClassLoader cl = packageInfo.getClassLoader(); + agent = (BackupAgent) cl.loadClass(data.appInfo.backupAgentName).newInstance(); + + // set up the agent's context + if (DEBUG_BACKUP) Log.v(TAG, "Initializing BackupAgent " + + data.appInfo.backupAgentName); + + ApplicationContext context = new ApplicationContext(); + context.init(packageInfo, null, this); + context.setOuterContext(agent); + agent.attach(context); + + agent.onCreate(); + binder = agent.onBind(); + mBackupAgents.put(packageName, agent); + } catch (Exception e) { + // If this is during restore, fail silently; otherwise go + // ahead and let the user see the crash. + Log.e(TAG, "Agent threw during creation: " + e); + if (data.backupMode != IApplicationThread.BACKUP_MODE_RESTORE) { + throw e; + } + // falling through with 'binder' still null + } // tell the OS that we're live now - IBinder binder = agent.onBind(); try { ActivityManagerNative.getDefault().backupAgentCreated(packageName, binder); } catch (RemoteException e) { // nothing to do. } - mBackupAgents.put(packageName, agent); } catch (Exception e) { throw new RuntimeException("Unable to create BackupAgent " + data.appInfo.backupAgentName + ": " + e.toString(), e); diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java index 38ea686..afb2fe9 100644 --- a/core/java/android/app/ApplicationContext.java +++ b/core/java/android/app/ApplicationContext.java @@ -1341,21 +1341,8 @@ class ApplicationContext extends Context { if (pi != null) { ApplicationContext c = new ApplicationContext(); c.mRestricted = (flags & CONTEXT_RESTRICTED) == CONTEXT_RESTRICTED; - c.init(pi, null, mMainThread); + c.init(pi, null, mMainThread, mResources); if (c.mResources != null) { - Resources newRes = c.mResources; - if (mResources.getCompatibilityInfo().applicationScale != - newRes.getCompatibilityInfo().applicationScale) { - DisplayMetrics dm = mMainThread.getDisplayMetricsLocked(false); - c.mResources = new Resources(newRes.getAssets(), dm, - newRes.getConfiguration(), - mResources.getCompatibilityInfo().copy()); - if (DEBUG) { - Log.d(TAG, "loaded context has different scaling. Using container's" + - " compatiblity info:" + mResources.getDisplayMetrics()); - } - - } return c; } } @@ -1417,8 +1404,24 @@ class ApplicationContext extends Context { final void init(ActivityThread.PackageInfo packageInfo, IBinder activityToken, ActivityThread mainThread) { + init(packageInfo, activityToken, mainThread, null); + } + + final void init(ActivityThread.PackageInfo packageInfo, + IBinder activityToken, ActivityThread mainThread, + Resources container) { mPackageInfo = packageInfo; mResources = mPackageInfo.getResources(mainThread); + + if (container != null && container.getCompatibilityInfo().applicationScale != + mResources.getCompatibilityInfo().applicationScale) { + if (DEBUG) { + Log.d(TAG, "loaded context has different scaling. Using container's" + + " compatiblity info:" + container.getDisplayMetrics()); + } + mResources = mainThread.getTopLevelResources( + mPackageInfo.getResDir(), container.getCompatibilityInfo().copy()); + } mMainThread = mainThread; mContentResolver = new ApplicationContentResolver(this, mainThread); diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index b052c99..a3c6325 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -119,13 +119,15 @@ public abstract class ApplicationThreadNative extends Binder data.enforceInterface(IApplicationThread.descriptor); Intent intent = Intent.CREATOR.createFromParcel(data); IBinder b = data.readStrongBinder(); + int ident = data.readInt(); ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data); Bundle state = data.readBundle(); List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR); List<Intent> pi = data.createTypedArrayList(Intent.CREATOR); boolean notResumed = data.readInt() != 0; boolean isForward = data.readInt() != 0; - scheduleLaunchActivity(intent, b, info, state, ri, pi, notResumed, isForward); + scheduleLaunchActivity(intent, b, ident, info, state, ri, pi, + notResumed, isForward); return true; } @@ -442,7 +444,7 @@ class ApplicationThreadProxy implements IApplicationThread { data.recycle(); } - public final void scheduleLaunchActivity(Intent intent, IBinder token, + public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Bundle state, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, boolean notResumed, boolean isForward) throws RemoteException { @@ -450,6 +452,7 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeInterfaceToken(IApplicationThread.descriptor); intent.writeToParcel(data, 0); data.writeStrongBinder(token); + data.writeInt(ident); info.writeToParcel(data, 0); data.writeBundle(state); data.writeTypedList(pendingResults); diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 222fe75..9432755 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -20,6 +20,7 @@ import com.android.internal.policy.PolicyManager; import android.content.Context; import android.content.DialogInterface; +import android.content.ComponentName; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; @@ -86,6 +87,7 @@ public class Dialog implements DialogInterface, Window.Callback, private Message mCancelMessage; private Message mDismissMessage; + private Message mShowMessage; /** * Whether to cancel the dialog when a touch is received outside of the @@ -140,7 +142,7 @@ public class Dialog implements DialogInterface, Window.Callback, w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); mUiThread = Thread.currentThread(); - mDismissCancelHandler = new DismissCancelHandler(this); + mListenersHandler = new ListenersHandler(this); } /** @@ -235,6 +237,8 @@ public class Dialog implements DialogInterface, Window.Callback, } mWindowManager.addView(mDecor, l); mShowing = true; + + sendShowMessage(); } /** @@ -289,11 +293,20 @@ public class Dialog implements DialogInterface, Window.Callback, } } + private void sendShowMessage() { + if (mShowMessage != null) { + // Obtain a new message so this dialog can be re-used + Message.obtain(mShowMessage).sendToTarget(); + } + } + // internal method to make sure mcreated is set properly without requiring // users to call through to super in onCreate void dispatchOnCreate(Bundle savedInstanceState) { - onCreate(savedInstanceState); - mCreated = true; + if (!mCreated) { + onCreate(savedInstanceState); + mCreated = true; + } } /** @@ -772,8 +785,22 @@ public class Dialog implements DialogInterface, Window.Callback, * This hook is called when the user signals the desire to start a search. */ public boolean onSearchRequested() { - // not during dialogs, no. - return false; + final SearchManager searchManager = (SearchManager) mContext + .getSystemService(Context.SEARCH_SERVICE); + + // can't start search without an associated activity (e.g a system dialog) + if (!searchManager.hasIdent()) { + return false; + } + + // associate search with owner activity if possible (otherwise it will default to + // global search). + final ComponentName appName = mOwnerActivity == null ? null + : mOwnerActivity.getComponentName(); + final boolean globalSearch = (appName == null); + searchManager.startSearch(null, false, appName, null, globalSearch); + dismiss(); + return true; } @@ -888,7 +915,7 @@ public class Dialog implements DialogInterface, Window.Callback, */ public void setOnCancelListener(final OnCancelListener listener) { if (listener != null) { - mCancelMessage = mDismissCancelHandler.obtainMessage(CANCEL, listener); + mCancelMessage = mListenersHandler.obtainMessage(CANCEL, listener); } else { mCancelMessage = null; } @@ -909,13 +936,26 @@ public class Dialog implements DialogInterface, Window.Callback, */ public void setOnDismissListener(final OnDismissListener listener) { if (listener != null) { - mDismissMessage = mDismissCancelHandler.obtainMessage(DISMISS, listener); + mDismissMessage = mListenersHandler.obtainMessage(DISMISS, listener); } else { mDismissMessage = null; } } /** + * Sets a listener to be invoked when the dialog is shown. + * + * @hide Pending API council approval + */ + public void setOnShowListener(OnShowListener listener) { + if (listener != null) { + mShowMessage = mListenersHandler.obtainMessage(SHOW, listener); + } else { + mShowMessage = null; + } + } + + /** * Set a message to be sent when the dialog is dismissed. * @param msg The msg to send when the dialog is dismissed. */ @@ -949,13 +989,14 @@ public class Dialog implements DialogInterface, Window.Callback, private static final int DISMISS = 0x43; private static final int CANCEL = 0x44; + private static final int SHOW = 0x45; - private Handler mDismissCancelHandler; + private Handler mListenersHandler; - private static final class DismissCancelHandler extends Handler { + private static final class ListenersHandler extends Handler { private WeakReference<DialogInterface> mDialog; - public DismissCancelHandler(Dialog dialog) { + public ListenersHandler(Dialog dialog) { mDialog = new WeakReference<DialogInterface>(dialog); } @@ -968,6 +1009,9 @@ public class Dialog implements DialogInterface, Window.Callback, case CANCEL: ((OnCancelListener) msg.obj).onCancel(mDialog.get()); break; + case SHOW: + ((OnShowListener) msg.obj).onShow(mDialog.get()); + break; } } } diff --git a/core/java/android/app/IActivityController.aidl b/core/java/android/app/IActivityController.aidl new file mode 100644 index 0000000..8f6b252 --- /dev/null +++ b/core/java/android/app/IActivityController.aidl @@ -0,0 +1,55 @@ +/* +** +** Copyright 2009, 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.app; + +import android.content.Intent; + +/** + * Testing interface to monitor what is happening in the activity manager + * while tests are running. Not for normal application development. + * {@hide} + */ +interface IActivityController +{ + /** + * The system is trying to start an activity. Return true to allow + * it to be started as normal, or false to cancel/reject this activity. + */ + boolean activityStarting(in Intent intent, String pkg); + + /** + * The system is trying to return to an activity. Return true to allow + * it to be resumed as normal, or false to cancel/reject this activity. + */ + boolean activityResuming(String pkg); + + /** + * An application process has crashed (in Java). Return true for the + * normal error recovery (app crash dialog) to occur, false to kill + * it immediately. + */ + boolean appCrashed(String processName, int pid, String shortMsg, + String longMsg, in byte[] crashData); + + /** + * An application process is not responding. Return 0 to show the "app + * not responding" dialog, 1 to continue waiting, or -1 to kill it + * immediately. + */ + int appNotResponding(String processName, int pid, String processStats); +} diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index 3ec7938..95b376c 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -216,7 +216,7 @@ public interface IActivityManager extends IInterface { String packageName, boolean waitForDebugger, boolean persistent) throws RemoteException; public void setAlwaysFinish(boolean enabled) throws RemoteException; - public void setActivityWatcher(IActivityWatcher watcher) + public void setActivityController(IActivityController watcher) throws RemoteException; public void enterSafeMode() throws RemoteException; @@ -257,6 +257,16 @@ public interface IActivityManager extends IInterface { public void stopAppSwitches() throws RemoteException; public void resumeAppSwitches() throws RemoteException; + public void registerActivityWatcher(IActivityWatcher watcher) + throws RemoteException; + public void unregisterActivityWatcher(IActivityWatcher watcher) + throws RemoteException; + + public int startActivityInPackage(int uid, + Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, boolean onlyIfNeeded) + throws RemoteException; + /* * Private non-Binder interfaces */ @@ -372,7 +382,7 @@ public interface IActivityManager extends IInterface { int CHECK_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+53; int GRANT_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+54; int REVOKE_URI_PERMISSION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+55; - int SET_ACTIVITY_WATCHER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+56; + int SET_ACTIVITY_CONTROLLER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+56; int SHOW_WAITING_FOR_DEBUGGER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+57; int SIGNAL_PERSISTENT_PROCESSES_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+58; int GET_RECENT_TASKS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+59; @@ -408,4 +418,7 @@ public interface IActivityManager extends IInterface { int START_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+89; int BACKUP_AGENT_CREATED_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+90; int UNBIND_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+91; + int REGISTER_ACTIVITY_WATCHER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+92; + int UNREGISTER_ACTIVITY_WATCHER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+93; + int START_ACTIVITY_IN_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+94; } diff --git a/core/java/android/app/IActivityWatcher.aidl b/core/java/android/app/IActivityWatcher.aidl index f13a385..5d36e3f 100644 --- a/core/java/android/app/IActivityWatcher.aidl +++ b/core/java/android/app/IActivityWatcher.aidl @@ -1,6 +1,6 @@ -/* //device/java/android/android/app/IInstrumentationWatcher.aidl +/* ** -** Copyright 2007, The Android Open Source Project +** Copyright 2009, 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. @@ -17,39 +17,10 @@ package android.app; -import android.content.Intent; - /** - * Testing interface to monitor what is happening in the activity manager - * while tests are running. Not for normal application development. + * Callback interface to watch the user's traversal through activities. * {@hide} */ -interface IActivityWatcher -{ - /** - * The system is trying to start an activity. Return true to allow - * it to be started as normal, or false to cancel/reject this activity. - */ - boolean activityStarting(in Intent intent, String pkg); - - /** - * The system is trying to return to an activity. Return true to allow - * it to be resumed as normal, or false to cancel/reject this activity. - */ - boolean activityResuming(String pkg); - - /** - * An application process has crashed (in Java). Return true for the - * normal error recovery (app crash dialog) to occur, false to kill - * it immediately. - */ - boolean appCrashed(String processName, int pid, String shortMsg, - String longMsg, in byte[] crashData); - - /** - * An application process is not responding. Return 0 to show the "app - * not responding" dialog, 1 to continue waiting, or -1 to kill it - * immediately. - */ - int appNotResponding(String processName, int pid, String processStats); +oneway interface IActivityWatcher { + void activityResuming(int activityId); } diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index c0bc2a0..c915770 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -49,7 +49,7 @@ public interface IApplicationThread extends IInterface { void scheduleWindowVisibility(IBinder token, boolean showWindow) throws RemoteException; void scheduleResumeActivity(IBinder token, boolean isForward) throws RemoteException; void scheduleSendResult(IBinder token, List<ResultInfo> results) throws RemoteException; - void scheduleLaunchActivity(Intent intent, IBinder token, + void scheduleLaunchActivity(Intent intent, IBinder token, int ident, ActivityInfo info, Bundle state, List<ResultInfo> pendingResults, List<Intent> pendingNewIntents, boolean notResumed, boolean isForward) throws RemoteException; diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl index e8bd60a..bd72544 100644 --- a/core/java/android/app/ISearchManager.aidl +++ b/core/java/android/app/ISearchManager.aidl @@ -34,10 +34,8 @@ interface ISearchManager { in ComponentName launchActivity, in Bundle appSearchData, boolean globalSearch, - ISearchManagerCallback searchManagerCallback); + ISearchManagerCallback searchManagerCallback, + int ident); void stopSearch(); boolean isVisible(); - Bundle onSaveInstanceState(); - void onRestoreInstanceState(in Bundle savedInstanceState); - void onConfigurationChanged(in Configuration newConfig); } diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index fdb619a..27c6376 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -19,22 +19,19 @@ package android.app; import static android.app.SuggestionsAdapter.getColumnString; import android.content.ActivityNotFoundException; -import android.content.BroadcastReceiver; import android.content.ComponentName; -import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.pm.PackageManager.NameNotFoundException; -import android.content.res.Configuration; import android.content.res.Resources; import android.database.Cursor; -import android.graphics.drawable.Drawable; import android.graphics.drawable.Animatable; +import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; import android.os.SystemClock; @@ -92,14 +89,13 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS private static final String INSTANCE_KEY_STORED_APPDATA = "sData"; private static final String INSTANCE_KEY_PREVIOUS_COMPONENTS = "sPrev"; private static final String INSTANCE_KEY_USER_QUERY = "uQry"; + + // The extra key used in an intent to the speech recognizer for in-app voice search. + private static final String EXTRA_CALLING_PACKAGE = "calling_package"; private static final int SEARCH_PLATE_LEFT_PADDING_GLOBAL = 12; private static final int SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL = 7; - - // interaction with runtime - private IntentFilter mCloseDialogsFilter; - private IntentFilter mPackageFilter; - + // views & widgets private TextView mBadgeLabel; private ImageView mAppIcon; @@ -143,8 +139,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // A weak map of drawables we've gotten from other packages, so we don't load them // more than once. - private final WeakHashMap<String, Drawable> mOutsideDrawablesCache = - new WeakHashMap<String, Drawable>(); + private final WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache = + new WeakHashMap<String, Drawable.ConstantState>(); // Last known IME options value for the search edit text. private int mSearchAutoCompleteImeOptions; @@ -210,15 +206,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // Touching outside of the search dialog will dismiss it setCanceledOnTouchOutside(true); - - // Set up broadcast filters - mCloseDialogsFilter = new IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); - mPackageFilter = new IntentFilter(); - mPackageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); - mPackageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); - mPackageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); - mPackageFilter.addDataScheme("package"); - + // Save voice intent for later queries/launching mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -333,16 +321,14 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS if (!globalSearch && mSearchable == null) { globalSearch = true; mSearchable = searchManager.getSearchableInfo(componentName, globalSearch); - - // If we still get back null (i.e., there's not even a searchable info available - // for global search), then really give up. - if (mSearchable == null) { - // Unfortunately, we can't log here. it would be logspam every time the user - // clicks the "search" key on a non-search app. - return false; - } } - + + // If there's not even a searchable info available for global search, then really give up. + if (mSearchable == null) { + Log.w(LOG_TAG, "No global search provider."); + return false; + } + mLaunchComponent = componentName; mAppSearchData = appSearchData; // Using globalSearch here is just an optimization, just calling @@ -351,16 +337,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS mActivityContext = mSearchable.getActivityContext(getContext()); // show the dialog. this will call onStart(). - if (!isShowing()) { - // First make sure the keyboard is showing (if needed), so that we get the right height - // for the dropdown to respect the IME. - if (getContext().getResources().getConfiguration().hardKeyboardHidden == - Configuration.HARDKEYBOARDHIDDEN_YES) { - InputMethodManager inputManager = (InputMethodManager) - getContext().getSystemService(Context.INPUT_METHOD_SERVICE); - inputManager.showSoftInputUnchecked(0, null); - } - + if (!isShowing()) { // The Dialog uses a ContextThemeWrapper for the context; use this to change the // theme out from underneath us, between the global search theme and the in-app // search theme. They are identical except that the global search theme does not @@ -377,20 +354,10 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS } show(); } - updateUI(); return true; } - - @Override - protected void onStart() { - super.onStart(); - - // receive broadcasts - getContext().registerReceiver(mBroadcastReceiver, mCloseDialogsFilter); - getContext().registerReceiver(mBroadcastReceiver, mPackageFilter); - } /** * The search dialog is being dismissed, so handle all of the local shutdown operations. @@ -401,14 +368,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS @Override public void onStop() { super.onStop(); - - // stop receiving broadcasts (throws exception if none registered) - try { - getContext().unregisterReceiver(mBroadcastReceiver); - } catch (RuntimeException e) { - // This is OK - it just means we didn't have any registered - } - + closeSuggestionsAdapter(); // dump extra memory we're hanging on to @@ -455,12 +415,15 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS /** * Save the minimal set of data necessary to recreate the search * - * @return A bundle with the state of the dialog. + * @return A bundle with the state of the dialog, or {@code null} if the search + * dialog is not showing. */ @Override public Bundle onSaveInstanceState() { + if (!isShowing()) return null; + Bundle bundle = new Bundle(); - + // setup info so I can recreate this particular search bundle.putParcelable(INSTANCE_KEY_COMPONENT, mLaunchComponent); bundle.putBundle(INSTANCE_KEY_APPDATA, mAppSearchData); @@ -483,6 +446,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS */ @Override public void onRestoreInstanceState(Bundle savedInstanceState) { + if (savedInstanceState == null) return; + ComponentName launchComponent = savedInstanceState.getParcelable(INSTANCE_KEY_COMPONENT); Bundle appSearchData = savedInstanceState.getBundle(INSTANCE_KEY_APPDATA); boolean globalSearch = savedInstanceState.getBoolean(INSTANCE_KEY_GLOBALSEARCH); @@ -509,7 +474,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS /** * Called after resources have changed, e.g. after screen rotation or locale change. */ - public void onConfigurationChanged(Configuration newConfig) { + public void onConfigurationChanged() { if (isShowing()) { // Redraw (resources may have changed) updateSearchButton(); @@ -524,6 +489,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS */ private void updateUI() { if (mSearchable != null) { + mDecor.setVisibility(View.VISIBLE); updateSearchAutoComplete(); updateSearchButton(); updateSearchAppIcon(); @@ -733,7 +699,10 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (DBG) Log.d(LOG_TAG, "onKeyDown(" + keyCode + "," + event + ")"); - + if (mSearchable == null) { + return false; + } + // handle back key to go back to previous searchable, etc. if (handleBackKey(keyCode, event)) { return true; @@ -769,6 +738,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS if (DBG_LOG_TIMING) { dbgLogTiming("onTextChanged()"); } + if (mSearchable == null) { + return; + } updateWidgetState(); if (!mSearchAutoComplete.isPerformingCompletion()) { // The user changed the query, remember it. @@ -777,7 +749,10 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS } public void afterTextChanged(Editable s) { - if (!mSearchAutoComplete.isPerformingCompletion()) { + if (mSearchable == null) { + return; + } + if (mSearchable.autoUrlDetect() && !mSearchAutoComplete.isPerformingCompletion()) { // The user changed the query, check if it is a URL and if so change the search // button in the soft keyboard to the 'Go' button. int options = (mSearchAutoComplete.getImeOptions() & (~EditorInfo.IME_MASK_ACTION)); @@ -878,11 +853,13 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS * @return A completely-configured intent ready to send to the voice search activity */ private Intent createVoiceAppSearchIntent(Intent baseIntent) { + ComponentName searchActivity = mSearchable.getSearchActivity(); + // create the necessary intent to set up a search-and-forward operation // in the voice search system. We have to keep the bundle separate, // because it becomes immutable once it enters the PendingIntent Intent queryIntent = new Intent(Intent.ACTION_SEARCH); - queryIntent.setComponent(mSearchable.getSearchActivity()); + queryIntent.setComponent(searchActivity); PendingIntent pending = PendingIntent.getActivity( getContext(), 0, queryIntent, PendingIntent.FLAG_ONE_SHOT); @@ -922,6 +899,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS voiceIntent.putExtra(RecognizerIntent.EXTRA_PROMPT, prompt); voiceIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE, language); voiceIntent.putExtra(RecognizerIntent.EXTRA_MAX_RESULTS, maxResults); + voiceIntent.putExtra(EXTRA_CALLING_PACKAGE, + searchActivity == null ? null : searchActivity.toShortString()); // Add the values that configure forwarding the results voiceIntent.putExtra(RecognizerIntent.EXTRA_RESULTS_PENDINGINTENT, pending); @@ -987,10 +966,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS && event.getAction() == KeyEvent.ACTION_UP) { v.cancelLongPress(); - // If this is a url entered by the user and we displayed the 'Go' button which + // If this is a url entered by the user & we displayed the 'Go' button which // the user clicked, launch the url instead of using it as a search query. - if ((mSearchAutoCompleteImeOptions & EditorInfo.IME_MASK_ACTION) - == EditorInfo.IME_ACTION_GO) { + if (mSearchable.autoUrlDetect() && + (mSearchAutoCompleteImeOptions & EditorInfo.IME_MASK_ACTION) + == EditorInfo.IME_ACTION_GO) { Uri uri = Uri.parse(fixUrl(mSearchAutoComplete.getText().toString())); Intent intent = new Intent(Intent.ACTION_VIEW, uri); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); @@ -1012,35 +992,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS return false; } }; - - /** - * When the ACTION_CLOSE_SYSTEM_DIALOGS intent is received, we should close ourselves - * immediately, in order to allow a higher-priority UI to take over - * (e.g. phone call received). - * - * When a package is added, removed or changed, our current context - * may no longer be valid. This would only happen if a package is installed/removed exactly - * when the search bar is open. So for now we're just going to close the search - * bar. - * Anything fancier would require some checks to see if the user's context was still valid. - * Which would be messier. - */ - private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - String action = intent.getAction(); - if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { - cancel(); - } else if (Intent.ACTION_PACKAGE_ADDED.equals(action) - || Intent.ACTION_PACKAGE_REMOVED.equals(action) - || Intent.ACTION_PACKAGE_CHANGED.equals(action)) { - cancel(); - } - } - }; @Override - public void cancel() { + public void hide() { + if (!isShowing()) return; + // We made sure the IME was displayed, so also make sure it is closed // when we go away. InputMethodManager imm = (InputMethodManager)getContext() @@ -1049,10 +1005,10 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS imm.hideSoftInputFromWindow( getWindow().getDecorView().getWindowToken(), 0); } - - super.cancel(); + + super.hide(); } - + /** * React to the user typing while in the suggestions list. First, check for action * keys. If not handled, try refocusing regular characters into the EditText. @@ -1086,6 +1042,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS mSearchAutoComplete.setSelection(selPoint); mSearchAutoComplete.setListSelection(0); mSearchAutoComplete.clearListSelection(); + mSearchAutoComplete.ensureImeVisible(); + return true; } @@ -1276,8 +1234,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS } /** - * Launches an intent and dismisses the search dialog (unless the intent - * is one of the special intents that modifies the state of the search dialog). + * Launches an intent, including any special intent handling. Doesn't dismiss the dialog + * since that will be handled in {@link SearchDialogWrapper#performActivityResuming} */ private void launchIntent(Intent intent) { if (intent == null) { @@ -1286,8 +1244,15 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS if (handleSpecialIntent(intent)){ return; } - dismiss(); + Log.d(LOG_TAG, "launching " + intent); getContext().startActivity(intent); + + // in global search mode, SearchDialogWrapper#performActivityResuming will handle hiding + // the dialog when the next activity starts, but for in-app search, we still need to + // dismiss the dialog. + if (!mGlobalSearchMode) { + dismiss(); + } } /** @@ -1580,7 +1545,22 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS @Override public void performCompletion() { } - + + /** + * We override this method to be sure and show the soft keyboard if appropriate when + * the TextView has focus. + */ + @Override + public void onWindowFocusChanged(boolean hasWindowFocus) { + super.onWindowFocusChanged(hasWindowFocus); + + if (hasWindowFocus) { + InputMethodManager inputManager = (InputMethodManager) + getContext().getSystemService(Context.INPUT_METHOD_SERVICE); + inputManager.showSoftInput(this, 0); + } + } + /** * We override this method so that we can allow a threshold of zero, which ACTV does not. */ @@ -1595,6 +1575,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS */ @Override public boolean onKeyPreIme(int keyCode, KeyEvent event) { + if (mSearchDialog.mSearchable == null) { + return false; + } if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { if (mSearchDialog.backToPreviousComponent()) { return true; diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index e5ba6a4..0631ad5 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -20,7 +20,6 @@ import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.DialogInterface; -import android.content.res.Configuration; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; @@ -1189,8 +1188,6 @@ public class SearchManager /** * Intent extra data key: This key will be used for the extra populated by the * {@link #SUGGEST_COLUMN_INTENT_EXTRA_DATA} column. - * - * {@hide} */ public final static String EXTRA_DATA_KEY = "intent_extra_data_key"; @@ -1270,16 +1267,12 @@ public class SearchManager * result indicates the shortcut refers to a no longer valid sugggestion. * * @see #SUGGEST_COLUMN_SHORTCUT_ID - * - * @hide pending API council approval */ public final static String SUGGEST_URI_PATH_SHORTCUT = "search_suggest_shortcut"; /** * MIME type for shortcut validation. You'll use this in your suggestions content provider * in the getType() function. - * - * @hide pending API council approval */ public final static String SHORTCUT_MIME_TYPE = "vnd.android.cursor.item/vnd.android.search.suggest"; @@ -1390,9 +1383,7 @@ public class SearchManager * this element exists at the given row, this is the data that will be used when * forming the suggestion's intent. If not provided, the Intent's extra data field will be null. * This column allows suggestions to provide additional arbitrary data which will be included as - * an extra under the key EXTRA_DATA_KEY. - * - * @hide Pending API council approval. + * an extra under the key {@link #EXTRA_DATA_KEY}. */ public final static String SUGGEST_COLUMN_INTENT_EXTRA_DATA = "suggest_intent_extra_data"; /** @@ -1426,8 +1417,6 @@ public class SearchManager * {@link #SUGGEST_NEVER_MAKE_SHORTCUT}, the result will not be stored as a shortcut. * Otherwise, the shortcut id will be used to check back for validation via * {@link #SUGGEST_URI_PATH_SHORTCUT}. - * - * @hide Pending API council approval. */ public final static String SUGGEST_COLUMN_SHORTCUT_ID = "suggest_shortcut_id"; @@ -1444,8 +1433,6 @@ public class SearchManager * Column name for suggestions cursor. <i>Optional.</i> This column is used to specify * that a spinner should be shown in lieu of an icon2 while the shortcut of this suggestion * is being refreshed. - * - * @hide Pending API council approval. */ public final static String SUGGEST_COLUMN_SPINNER_WHILE_REFRESHING = "suggest_spinner_while_refreshing"; @@ -1453,8 +1440,6 @@ public class SearchManager /** * Column value for suggestion column {@link #SUGGEST_COLUMN_SHORTCUT_ID} when a suggestion * should not be stored as a shortcut in global search. - * - * @hide Pending API council approval. */ public final static String SUGGEST_NEVER_MAKE_SHORTCUT = "_-1"; @@ -1501,8 +1486,6 @@ public class SearchManager * Intent action for starting a web search provider's settings activity. * Web search providers should handle this intent if they have provider-specific * settings to implement. - * - * @hide Pending API council approval. */ public final static String INTENT_ACTION_WEB_SEARCH_SETTINGS = "android.search.action.WEB_SEARCH_SETTINGS"; @@ -1511,11 +1494,17 @@ public class SearchManager * Intent action broadcasted to inform that the searchables list or default have changed. * Components should handle this intent if they cache any searchable data and wish to stay * up to date on changes. - * - * @hide Pending API council approval. */ public final static String INTENT_ACTION_SEARCHABLES_CHANGED = "android.search.action.SEARCHABLES_CHANGED"; + + /** + * Intent action broadcasted to inform that the search settings have changed in some way. + * Either searchables have been enabled or disabled, or a different web search provider + * has been chosen. + */ + public final static String INTENT_ACTION_SEARCH_SETTINGS_CHANGED + = "android.search.action.SETTINGS_CHANGED"; /** * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION}, @@ -1532,8 +1521,9 @@ public class SearchManager private final Context mContext; + private int mIdent; + // package private since they are used by the inner class SearchManagerCallback - /* package */ boolean mIsShowing = false; /* package */ final Handler mHandler; /* package */ OnDismissListener mDismissListener = null; /* package */ OnCancelListener mCancelListener = null; @@ -1546,6 +1536,17 @@ public class SearchManager mService = ISearchManager.Stub.asInterface( ServiceManager.getService(Context.SEARCH_SERVICE)); } + + /*package*/ boolean hasIdent() { + return mIdent != 0; + } + + /*package*/ void setIdent(int ident) { + if (mIdent != 0) { + throw new IllegalStateException("mIdent already set"); + } + mIdent = ident; + } /** * Launch search UI. @@ -1592,13 +1593,12 @@ public class SearchManager ComponentName launchActivity, Bundle appSearchData, boolean globalSearch) { - if (DBG) debug("startSearch(), mIsShowing=" + mIsShowing); - if (mIsShowing) return; + if (mIdent == 0) throw new IllegalArgumentException( + "Called from outside of an Activity context"); try { - mIsShowing = true; // activate the search manager and start it up! mService.startSearch(initialQuery, selectInitialQuery, launchActivity, appSearchData, - globalSearch, mSearchManagerCallback); + globalSearch, mSearchManagerCallback, mIdent); } catch (RemoteException ex) { Log.e(TAG, "startSearch() failed: " + ex); } @@ -1616,15 +1616,10 @@ public class SearchManager * @see #startSearch */ public void stopSearch() { - if (DBG) debug("stopSearch(), mIsShowing=" + mIsShowing); - if (!mIsShowing) return; + if (DBG) debug("stopSearch()"); try { mService.stopSearch(); - // onDismiss will also clear this, but we do it here too since onDismiss() is - // called asynchronously. - mIsShowing = false; } catch (RemoteException ex) { - Log.e(TAG, "stopSearch() failed: " + ex); } } @@ -1638,8 +1633,13 @@ public class SearchManager * @hide */ public boolean isVisible() { - if (DBG) debug("isVisible(), mIsShowing=" + mIsShowing); - return mIsShowing; + if (DBG) debug("isVisible()"); + try { + return mService.isVisible(); + } catch (RemoteException e) { + Log.e(TAG, "isVisible() failed: " + e); + return false; + } } /** @@ -1691,7 +1691,6 @@ public class SearchManager private final Runnable mFireOnDismiss = new Runnable() { public void run() { if (DBG) debug("mFireOnDismiss"); - mIsShowing = false; if (mDismissListener != null) { mDismissListener.onDismiss(); } @@ -1701,7 +1700,6 @@ public class SearchManager private final Runnable mFireOnCancel = new Runnable() { public void run() { if (DBG) debug("mFireOnCancel"); - // doesn't need to clear mIsShowing since onDismiss() always gets called too if (mCancelListener != null) { mCancelListener.onCancel(); } @@ -1720,66 +1718,18 @@ public class SearchManager } - // TODO: remove the DialogInterface interfaces from SearchManager. - // This changes the public API, so I'll do it in a separate change. - public void onCancel(DialogInterface dialog) { - throw new UnsupportedOperationException(); - } - public void onDismiss(DialogInterface dialog) { - throw new UnsupportedOperationException(); - } - - /** - * Saves the state of the search UI. - * - * @return A Bundle containing the state of the search dialog, or {@code null} - * if the search UI is not visible. - * - * @hide - */ - public Bundle saveSearchDialog() { - if (DBG) debug("saveSearchDialog(), mIsShowing=" + mIsShowing); - if (!mIsShowing) return null; - try { - return mService.onSaveInstanceState(); - } catch (RemoteException ex) { - Log.e(TAG, "onSaveInstanceState() failed: " + ex); - return null; - } - } - /** - * Restores the state of the search dialog. - * - * @param searchDialogState Bundle to read the state from. - * - * @hide + * @deprecated This method is an obsolete internal implementation detail. Do not use. */ - public void restoreSearchDialog(Bundle searchDialogState) { - if (DBG) debug("restoreSearchDialog(" + searchDialogState + ")"); - if (searchDialogState == null) return; - try { - mService.onRestoreInstanceState(searchDialogState); - } catch (RemoteException ex) { - Log.e(TAG, "onRestoreInstanceState() failed: " + ex); - } + public void onCancel(DialogInterface dialog) { + throw new UnsupportedOperationException(); } /** - * Update the search dialog after a configuration change. - * - * @param newConfig The new configuration. - * - * @hide + * @deprecated This method is an obsolete internal implementation detail. Do not use. */ - public void onConfigurationChanged(Configuration newConfig) { - if (DBG) debug("onConfigurationChanged(" + newConfig + "), mIsShowing=" + mIsShowing); - if (!mIsShowing) return; - try { - mService.onConfigurationChanged(newConfig); - } catch (RemoteException ex) { - Log.e(TAG, "onConfigurationChanged() failed:" + ex); - } + public void onDismiss(DialogInterface dialog) { + throw new UnsupportedOperationException(); } /** diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java index 49c94d1..593b7b7 100644 --- a/core/java/android/app/SuggestionsAdapter.java +++ b/core/java/android/app/SuggestionsAdapter.java @@ -16,28 +16,31 @@ package android.app; +import android.app.SearchManager.DialogCursorProtocol; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; -import android.content.res.Resources.NotFoundException; +import android.content.pm.ActivityInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.Resources; import android.database.Cursor; -import android.graphics.Canvas; +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.server.search.SearchableInfo; import android.text.Html; import android.text.TextUtils; -import android.util.DisplayMetrics; import android.util.Log; -import android.util.TypedValue; +import android.util.SparseArray; import android.view.View; import android.view.ViewGroup; -import android.widget.AbsListView; import android.widget.ImageView; import android.widget.ResourceCursorAdapter; import android.widget.TextView; - -import static android.app.SearchManager.DialogCursorProtocol; +import android.widget.Filter; import java.io.FileNotFoundException; import java.io.IOException; @@ -58,7 +61,8 @@ class SuggestionsAdapter extends ResourceCursorAdapter { private SearchDialog mSearchDialog; private SearchableInfo mSearchable; private Context mProviderContext; - private WeakHashMap<String, Drawable> mOutsideDrawablesCache; + private WeakHashMap<String, Drawable.ConstantState> mOutsideDrawablesCache; + private SparseArray<Drawable.ConstantState> mBackgroundsCache; private boolean mGlobalSearchMode; // Cached column indexes, updated when the cursor changes. @@ -87,8 +91,16 @@ class SuggestionsAdapter extends ResourceCursorAdapter { private final Runnable mStartSpinnerRunnable; private final Runnable mStopSpinnerRunnable; - public SuggestionsAdapter(Context context, SearchDialog searchDialog, SearchableInfo searchable, - WeakHashMap<String, Drawable> outsideDrawablesCache, boolean globalSearchMode) { + /** + * The amount of time we delay in the filter when the user presses the delete key. + * @see Filter#setDelayer(android.widget.Filter.Delayer). + */ + private static final long DELETE_KEY_POST_DELAY = 500L; + + public SuggestionsAdapter(Context context, SearchDialog searchDialog, + SearchableInfo searchable, + WeakHashMap<String, Drawable.ConstantState> outsideDrawablesCache, + boolean globalSearchMode) { super(context, com.android.internal.R.layout.search_dropdown_item_icons_2line, null, // no initial cursor @@ -102,6 +114,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter { mProviderContext = mSearchable.getProviderContext(mContext, activityContext); mOutsideDrawablesCache = outsideDrawablesCache; + mBackgroundsCache = new SparseArray<Drawable.ConstantState>(); mGlobalSearchMode = globalSearchMode; mStartSpinnerRunnable = new Runnable() { @@ -115,6 +128,18 @@ class SuggestionsAdapter extends ResourceCursorAdapter { mSearchDialog.setWorking(false); } }; + + // delay 500ms when deleting + getFilter().setDelayer(new Filter.Delayer() { + + private int mPreviousLength = 0; + + public long getPostingDelay(CharSequence constraint) { + long delay = constraint.length() < mPreviousLength ? DELETE_KEY_POST_DELAY : 0; + mPreviousLength = constraint.length(); + return delay; + } + }); } /** @@ -252,7 +277,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter { */ @Override public View newView(Context context, Cursor cursor, ViewGroup parent) { - View v = new SuggestionItemView(context, cursor); + View v = super.newView(context, cursor, parent); v.setTag(new ChildViewCache(v)); return v; } @@ -297,13 +322,46 @@ class SuggestionsAdapter extends ResourceCursorAdapter { if (mBackgroundColorCol != -1) { backgroundColor = cursor.getInt(mBackgroundColorCol); } - ((SuggestionItemView)view).setColor(backgroundColor); + Drawable background = getItemBackground(backgroundColor); + view.setBackgroundDrawable(background); final boolean isHtml = mFormatCol > 0 && "html".equals(cursor.getString(mFormatCol)); setViewText(cursor, views.mText1, mText1Col, isHtml); setViewText(cursor, views.mText2, mText2Col, isHtml); - setViewIcon(cursor, views.mIcon1, mIconName1Col); - setViewIcon(cursor, views.mIcon2, mIconName2Col); + + if (views.mIcon1 != null) { + setViewDrawable(views.mIcon1, getIcon1(cursor)); + } + if (views.mIcon2 != null) { + setViewDrawable(views.mIcon2, getIcon2(cursor)); + } + } + + /** + * Gets a drawable with no color when selected or pressed, and the given color when + * neither selected nor pressed. + * + * @return A drawable, or {@code null} if the given color is transparent. + */ + private Drawable getItemBackground(int backgroundColor) { + if (backgroundColor == 0) { + return null; + } else { + Drawable.ConstantState cachedBg = mBackgroundsCache.get(backgroundColor); + if (cachedBg != null) { + if (DBG) Log.d(LOG_TAG, "Background cache hit for color " + backgroundColor); + return cachedBg.newDrawable(); + } + if (DBG) Log.d(LOG_TAG, "Creating new background for color " + backgroundColor); + ColorDrawable transparent = new ColorDrawable(0); + ColorDrawable background = new ColorDrawable(backgroundColor); + StateListDrawable newBg = new StateListDrawable(); + newBg.addState(new int[]{android.R.attr.state_selected}, transparent); + newBg.addState(new int[]{android.R.attr.state_pressed}, transparent); + newBg.addState(new int[]{}, background); + mBackgroundsCache.put(backgroundColor, newBg.getConstantState()); + return newBg; + } } private void setViewText(Cursor cursor, TextView v, int textCol, boolean isHtml) { @@ -313,7 +371,11 @@ class SuggestionsAdapter extends ResourceCursorAdapter { CharSequence text = null; if (textCol >= 0) { String str = cursor.getString(textCol); - text = (str != null && isHtml) ? Html.fromHtml(str) : str; + if (isHtml && looksLikeHtml(str)) { + text = Html.fromHtml(str); + } else { + text = str; + } } // Set the text even if it's null, since we need to clear any previous text. v.setText(text); @@ -325,15 +387,40 @@ class SuggestionsAdapter extends ResourceCursorAdapter { } } - private void setViewIcon(Cursor cursor, ImageView v, int iconNameCol) { - if (v == null) { - return; + private static boolean looksLikeHtml(String str) { + if (TextUtils.isEmpty(str)) return false; + for (int i = str.length() - 1; i >= 0; i--) { + char c = str.charAt(i); + if (c == '<' || c == '&') return true; } - if (iconNameCol < 0) { - return; + return false; + } + + private Drawable getIcon1(Cursor cursor) { + if (mIconName1Col < 0) { + return null; } - String value = cursor.getString(iconNameCol); + String value = cursor.getString(mIconName1Col); Drawable drawable = getDrawableFromResourceValue(value); + if (drawable != null) { + return drawable; + } + return getDefaultIcon1(cursor); + } + + private Drawable getIcon2(Cursor cursor) { + if (mIconName2Col < 0) { + return null; + } + String value = cursor.getString(mIconName2Col); + return getDrawableFromResourceValue(value); + } + + /** + * Sets the drawable in an image view, makes sure the view is only visible if there + * is a drawable. + */ + private void setViewDrawable(ImageView v, Drawable drawable) { // Set the icon even if the drawable is null, since we need to clear any // previous icon. v.setImageDrawable(drawable); @@ -437,12 +524,13 @@ class SuggestionsAdapter extends ResourceCursorAdapter { } // First, check the cache. - Drawable drawable = mOutsideDrawablesCache.get(drawableId); - if (drawable != null) { + Drawable.ConstantState cached = mOutsideDrawablesCache.get(drawableId); + if (cached != null) { if (DBG) Log.d(LOG_TAG, "Found icon in cache: " + drawableId); - return drawable; + return cached.newDrawable(); } + Drawable drawable = null; try { // Not cached, try using it as a plain resource ID in the provider's context. int resourceId = Integer.parseInt(drawableId); @@ -474,9 +562,9 @@ class SuggestionsAdapter extends ResourceCursorAdapter { // If we got a drawable for this resource id, then stick it in the // map so we don't do this lookup again. if (drawable != null) { - mOutsideDrawablesCache.put(drawableId, drawable); + mOutsideDrawablesCache.put(drawableId, drawable.getConstantState()); } - } catch (NotFoundException nfe) { + } catch (Resources.NotFoundException nfe) { if (DBG) Log.d(LOG_TAG, "Icon resource not found: " + drawableId); // drawable = null; } @@ -485,84 +573,103 @@ class SuggestionsAdapter extends ResourceCursorAdapter { } /** - * Gets the value of a string column by name. + * Gets the left-hand side icon that will be used for the current suggestion + * if the suggestion contains an icon column but no icon or a broken icon. * - * @param cursor Cursor to read the value from. - * @param columnName The name of the column to read. - * @return The value of the given column, or <code>null</null> - * if the cursor does not contain the given column. + * @param cursor A cursor positioned at the current suggestion. + * @return A non-null drawable. */ - public static String getColumnString(Cursor cursor, String columnName) { - int col = cursor.getColumnIndex(columnName); - if (col == NONE) { - return null; + private Drawable getDefaultIcon1(Cursor cursor) { + // First check the component that the suggestion is originally from + String c = getColumnString(cursor, SearchManager.SUGGEST_COLUMN_INTENT_COMPONENT_NAME); + if (c != null) { + ComponentName component = ComponentName.unflattenFromString(c); + if (component != null) { + Drawable drawable = getActivityIconWithCache(component); + if (drawable != null) { + return drawable; + } + } else { + Log.w(LOG_TAG, "Bad component name: " + c); + } } - return cursor.getString(col); + + // Then check the component that gave us the suggestion + Drawable drawable = getActivityIconWithCache(mSearchable.getSearchActivity()); + if (drawable != null) { + return drawable; + } + + // Fall back to a default icon + return mContext.getPackageManager().getDefaultActivityIcon(); } /** - * A parent viewgroup class which holds the actual suggestion item as a child. + * Gets the activity or application icon for an activity. + * Uses the local icon cache for fast repeated lookups. * - * The sole purpose of this class is to draw the given background color when the item is in - * normal state and not draw the background color when it is pressed, so that when pressed the - * list view's selection highlight will be displayed properly (if we draw our background it - * draws on top of the list view selection highlight). + * @param component Name of an activity. + * @return A drawable, or {@code null} if neither the activity nor the application + * has an icon set. */ - private class SuggestionItemView extends ViewGroup { - private int mBackgroundColor; // the background color to draw in normal state. - private View mView; // the suggestion item's view. - - protected SuggestionItemView(Context context, Cursor cursor) { - // Initialize ourselves - super(context); - mBackgroundColor = 0; // transparent by default. - - // For our layout use the default list item height from the current theme. - TypedValue lineHeight = new TypedValue(); - context.getTheme().resolveAttribute( - com.android.internal.R.attr.searchResultListItemHeight, lineHeight, true); - DisplayMetrics metrics = new DisplayMetrics(); - metrics.setToDefaults(); - AbsListView.LayoutParams layout = new AbsListView.LayoutParams( - AbsListView.LayoutParams.FILL_PARENT, - (int)lineHeight.getDimension(metrics)); - - setLayoutParams(layout); - - // Initialize the child view - mView = SuggestionsAdapter.super.newView(context, cursor, this); - if (mView != null) { - addView(mView, layout.width, layout.height); - mView.setVisibility(View.VISIBLE); - } - } - - public void setColor(int backgroundColor) { - mBackgroundColor = backgroundColor; + private Drawable getActivityIconWithCache(ComponentName component) { + // First check the icon cache + String componentIconKey = component.flattenToShortString(); + // Using containsKey() since we also store null values. + if (mOutsideDrawablesCache.containsKey(componentIconKey)) { + Drawable.ConstantState cached = mOutsideDrawablesCache.get(componentIconKey); + return cached == null ? null : cached.newDrawable(); } + // Then try the activity or application icon + Drawable drawable = getActivityIcon(component); + // Stick it in the cache so we don't do this lookup again. + Drawable.ConstantState toCache = drawable == null ? null : drawable.getConstantState(); + mOutsideDrawablesCache.put(componentIconKey, toCache); + return drawable; + } - @Override - public void dispatchDraw(Canvas canvas) { - if (mBackgroundColor != 0 && !isPressed() && !isSelected()) { - canvas.drawColor(mBackgroundColor); - } - super.dispatchDraw(canvas); + /** + * Gets the activity or application icon for an activity. + * + * @param component Name of an activity. + * @return A drawable, or {@code null} if neither the acitivy or the application + * have an icon set. + */ + private Drawable getActivityIcon(ComponentName component) { + PackageManager pm = mContext.getPackageManager(); + final ActivityInfo activityInfo; + try { + activityInfo = pm.getActivityInfo(component, PackageManager.GET_META_DATA); + } catch (NameNotFoundException ex) { + Log.w(LOG_TAG, ex.toString()); + return null; } - - @Override - protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { - super.onMeasure(widthMeasureSpec, heightMeasureSpec); - if (mView != null) { - mView.measure(widthMeasureSpec, heightMeasureSpec); - } + int iconId = activityInfo.getIconResource(); + if (iconId == 0) return null; + String pkg = component.getPackageName(); + Drawable drawable = pm.getDrawable(pkg, iconId, activityInfo.applicationInfo); + if (drawable == null) { + Log.w(LOG_TAG, "Invalid icon resource " + iconId + " for " + + component.flattenToShortString()); + return null; } + return drawable; + } - @Override - protected void onLayout(boolean changed, int left, int top, int right, int bottom) { - if (mView != null) { - mView.layout(0, 0, mView.getMeasuredWidth(), mView.getMeasuredHeight()); - } + /** + * Gets the value of a string column by name. + * + * @param cursor Cursor to read the value from. + * @param columnName The name of the column to read. + * @return The value of the given column, or <code>null</null> + * if the cursor does not contain the given column. + */ + public static String getColumnString(Cursor cursor, String columnName) { + int col = cursor.getColumnIndex(columnName); + if (col == NONE) { + return null; } + return cursor.getString(col); } } diff --git a/core/java/android/appwidget/AppWidgetProvider.java b/core/java/android/appwidget/AppWidgetProvider.java index 26712a1..f1bbede 100755 --- a/core/java/android/appwidget/AppWidgetProvider.java +++ b/core/java/android/appwidget/AppWidgetProvider.java @@ -64,11 +64,9 @@ public class AppWidgetProvider extends BroadcastReceiver { } else if (AppWidgetManager.ACTION_APPWIDGET_DELETED.equals(action)) { Bundle extras = intent.getExtras(); - if (extras != null) { - int[] appWidgetIds = extras.getIntArray(AppWidgetManager.EXTRA_APPWIDGET_IDS); - if (appWidgetIds != null && appWidgetIds.length > 0) { - this.onDeleted(context, appWidgetIds); - } + if (extras != null && extras.containsKey(AppWidgetManager.EXTRA_APPWIDGET_ID)) { + final int appWidgetId = extras.getInt(AppWidgetManager.EXTRA_APPWIDGET_ID); + this.onDeleted(context, new int[] { appWidgetId }); } } else if (AppWidgetManager.ACTION_APPWIDGET_ENABLED.equals(action)) { diff --git a/core/java/android/appwidget/AppWidgetProviderInfo.java b/core/java/android/appwidget/AppWidgetProviderInfo.java index 8530c35..a2e0ba0a 100644 --- a/core/java/android/appwidget/AppWidgetProviderInfo.java +++ b/core/java/android/appwidget/AppWidgetProviderInfo.java @@ -57,6 +57,9 @@ public class AppWidgetProviderInfo implements Parcelable { * * <p>This field corresponds to the <code>android:updatePeriodMillis</code> attribute in * the AppWidget meta-data file. + * + * <p class="note"><b>Note:</b> Updates requested with <code>updatePeriodMillis</code> + * will not be delivered more than once every 30 minutes.</p> */ public int updatePeriodMillis; diff --git a/core/java/android/backup/BackupManager.java b/core/java/android/backup/BackupManager.java index 34a1a0c..c52fcd2 100644 --- a/core/java/android/backup/BackupManager.java +++ b/core/java/android/backup/BackupManager.java @@ -42,6 +42,9 @@ import android.util.Log; public class BackupManager { private static final String TAG = "BackupManager"; + /** @hide TODO: REMOVE THIS */ + public static final boolean EVEN_THINK_ABOUT_DOING_RESTORE = false; + private Context mContext; private static IBackupManager sService; @@ -70,6 +73,9 @@ public class BackupManager { * {@link android.app.BackupAgent} subclass will be scheduled when you call this method. */ public void dataChanged() { + if (!EVEN_THINK_ABOUT_DOING_RESTORE) { + return; + } checkServiceBinder(); if (sService != null) { try { @@ -89,6 +95,9 @@ public class BackupManager { * permission if the package named in the argument is not the caller's own. */ public static void dataChanged(String packageName) { + if (!EVEN_THINK_ABOUT_DOING_RESTORE) { + return; + } checkServiceBinder(); if (sService != null) { try { @@ -107,6 +116,9 @@ public class BackupManager { * {@hide} */ public IRestoreSession beginRestoreSession(String transport) { + if (!EVEN_THINK_ABOUT_DOING_RESTORE) { + return null; + } IRestoreSession binder = null; checkServiceBinder(); if (sService != null) { diff --git a/core/java/android/content/AbstractTableMerger.java b/core/java/android/content/AbstractTableMerger.java index 9c760d9..9f609a3 100644 --- a/core/java/android/content/AbstractTableMerger.java +++ b/core/java/android/content/AbstractTableMerger.java @@ -369,30 +369,33 @@ public abstract class AbstractTableMerger // An existing server item has changed // If serverSyncVersion is null, there is no edit URL; // server won't let this change be written. - // Just hold onto it, I guess, in case the server permissions - // change later. - if (serverSyncVersion != null) { - boolean recordChanged = (localSyncVersion == null) || - !serverSyncVersion.equals(localSyncVersion); - if (recordChanged) { - if (localSyncDirty) { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, "remote record " + serverSyncId - + " conflicts with local _sync_id " + localSyncID - + ", local _id " + localRowId); - } - conflict = true; - } else { - if (Log.isLoggable(TAG, Log.VERBOSE)) { - Log.v(TAG, - "remote record " + - serverSyncId + - " updates local _sync_id " + - localSyncID + ", local _id " + - localRowId); - } - update = true; + boolean recordChanged = (localSyncVersion == null) || + (serverSyncVersion == null) || + !serverSyncVersion.equals(localSyncVersion); + if (recordChanged) { + if (localSyncDirty) { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "remote record " + serverSyncId + + " conflicts with local _sync_id " + localSyncID + + ", local _id " + localRowId); } + conflict = true; + } else { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, + "remote record " + + serverSyncId + + " updates local _sync_id " + + localSyncID + ", local _id " + + localRowId); + } + update = true; + } + } else { + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, + "Skipping update: localSyncVersion: " + localSyncVersion + + ", serverSyncVersion: " + serverSyncVersion); } } } else { diff --git a/core/java/android/content/ContentProvider.java b/core/java/android/content/ContentProvider.java index 5cc5730..6b50405 100644 --- a/core/java/android/content/ContentProvider.java +++ b/core/java/android/content/ContentProvider.java @@ -17,6 +17,7 @@ package android.content; import android.content.pm.PackageManager; +import android.content.pm.PathPermission; import android.content.pm.ProviderInfo; import android.content.res.AssetFileDescriptor; import android.content.res.Configuration; @@ -29,6 +30,7 @@ import android.database.SQLException; import android.net.Uri; import android.os.Binder; import android.os.ParcelFileDescriptor; +import android.os.Process; import java.io.File; import java.io.FileNotFoundException; @@ -65,8 +67,10 @@ import java.io.FileNotFoundException; */ public abstract class ContentProvider implements ComponentCallbacks { private Context mContext = null; + private int mMyUid; private String mReadPermission; private String mWritePermission; + private PathPermission[] mPathPermissions; private Transport mTransport = new Transport(); @@ -108,24 +112,20 @@ public abstract class ContentProvider implements ComponentCallbacks { public IBulkCursor bulkQuery(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder, IContentObserver observer, CursorWindow window) { - checkReadPermission(uri); + enforceReadPermission(uri); Cursor cursor = ContentProvider.this.query(uri, projection, selection, selectionArgs, sortOrder); if (cursor == null) { return null; } - String wperm = getWritePermission(); return new CursorToBulkCursorAdaptor(cursor, observer, ContentProvider.this.getClass().getName(), - wperm == null || - getContext().checkCallingOrSelfPermission(getWritePermission()) - == PackageManager.PERMISSION_GRANTED, - window); + hasWritePermission(uri), window); } public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { - checkReadPermission(uri); + enforceReadPermission(uri); return ContentProvider.this.query(uri, projection, selection, selectionArgs, sortOrder); } @@ -136,55 +136,84 @@ public abstract class ContentProvider implements ComponentCallbacks { public Uri insert(Uri uri, ContentValues initialValues) { - checkWritePermission(uri); + enforceWritePermission(uri); return ContentProvider.this.insert(uri, initialValues); } public int bulkInsert(Uri uri, ContentValues[] initialValues) { - checkWritePermission(uri); + enforceWritePermission(uri); return ContentProvider.this.bulkInsert(uri, initialValues); } public int delete(Uri uri, String selection, String[] selectionArgs) { - checkWritePermission(uri); + enforceWritePermission(uri); return ContentProvider.this.delete(uri, selection, selectionArgs); } public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) { - checkWritePermission(uri); + enforceWritePermission(uri); return ContentProvider.this.update(uri, values, selection, selectionArgs); } public ParcelFileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException { - if (mode != null && mode.startsWith("rw")) checkWritePermission(uri); - else checkReadPermission(uri); + if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri); + else enforceReadPermission(uri); return ContentProvider.this.openFile(uri, mode); } public AssetFileDescriptor openAssetFile(Uri uri, String mode) throws FileNotFoundException { - if (mode != null && mode.startsWith("rw")) checkWritePermission(uri); - else checkReadPermission(uri); + if (mode != null && mode.startsWith("rw")) enforceWritePermission(uri); + else enforceReadPermission(uri); return ContentProvider.this.openAssetFile(uri, mode); } public ISyncAdapter getSyncAdapter() { - checkWritePermission(null); + enforceWritePermission(null); SyncAdapter sa = ContentProvider.this.getSyncAdapter(); return sa != null ? sa.getISyncAdapter() : null; } - private void checkReadPermission(Uri uri) { + private void enforceReadPermission(Uri uri) { + final int uid = Binder.getCallingUid(); + if (uid == mMyUid) { + return; + } + + final Context context = getContext(); final String rperm = getReadPermission(); final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - if (getContext().checkUriPermission(uri, rperm, null, pid, uid, + if (rperm == null + || context.checkPermission(rperm, pid, uid) + == PackageManager.PERMISSION_GRANTED) { + return; + } + + PathPermission[] pps = getPathPermissions(); + if (pps != null) { + final String path = uri.getPath(); + int i = pps.length; + while (i > 0) { + i--; + final PathPermission pp = pps[i]; + final String pprperm = pp.getReadPermission(); + if (pprperm != null && pp.match(path)) { + if (context.checkPermission(pprperm, pid, uid) + == PackageManager.PERMISSION_GRANTED) { + return; + } + } + } + } + + if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_READ_URI_PERMISSION) == PackageManager.PERMISSION_GRANTED) { return; } + String msg = "Permission Denial: reading " + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + Binder.getCallingPid() @@ -193,20 +222,57 @@ public abstract class ContentProvider implements ComponentCallbacks { throw new SecurityException(msg); } - private void checkWritePermission(Uri uri) { + private boolean hasWritePermission(Uri uri) { + final int uid = Binder.getCallingUid(); + if (uid == mMyUid) { + return true; + } + + final Context context = getContext(); final String wperm = getWritePermission(); final int pid = Binder.getCallingPid(); - final int uid = Binder.getCallingUid(); - if (getContext().checkUriPermission(uri, null, wperm, pid, uid, + if (wperm == null + || context.checkPermission(wperm, pid, uid) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + + PathPermission[] pps = getPathPermissions(); + if (pps != null) { + final String path = uri.getPath(); + int i = pps.length; + while (i > 0) { + i--; + final PathPermission pp = pps[i]; + final String ppwperm = pp.getWritePermission(); + if (ppwperm != null && pp.match(path)) { + if (context.checkPermission(ppwperm, pid, uid) + == PackageManager.PERMISSION_GRANTED) { + return true; + } + } + } + } + + if (context.checkUriPermission(uri, pid, uid, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == PackageManager.PERMISSION_GRANTED) { + return true; + } + + return false; + } + + private void enforceWritePermission(Uri uri) { + if (hasWritePermission(uri)) { return; } + String msg = "Permission Denial: writing " + ContentProvider.this.getClass().getName() + " uri " + uri + " from pid=" + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() - + " requires " + wperm; + + " requires " + getWritePermission(); throw new SecurityException(msg); } } @@ -266,6 +332,28 @@ public abstract class ContentProvider implements ComponentCallbacks { } /** + * Change the path-based permission required to read and/or write data in + * the content provider. This is normally set for you from its manifest + * information when the provider is first created. + * + * @param permissions Array of path permission descriptions. + */ + protected final void setPathPermissions(PathPermission[] permissions) { + mPathPermissions = permissions; + } + + /** + * Return the path-based permissions required for read and/or write access to + * this content provider. This method can be called from multiple + * threads, as described in + * <a href="{@docRoot}guide/topics/fundamentals.html#procthread">Application Fundamentals: + * Processes and Threads</a>. + */ + public final PathPermission[] getPathPermissions() { + return mPathPermissions; + } + + /** * Called when the provider is being started. * * @return true if the provider was successfully loaded, false otherwise @@ -600,9 +688,11 @@ public abstract class ContentProvider implements ComponentCallbacks { */ if (mContext == null) { mContext = context; + mMyUid = Process.myUid(); if (info != null) { setReadPermission(info.readPermission); setWritePermission(info.writePermission); + setPathPermissions(info.pathPermissions); } ContentProvider.this.onCreate(); } diff --git a/core/java/android/content/DialogInterface.java b/core/java/android/content/DialogInterface.java index 4afa294..9f1036e 100644 --- a/core/java/android/content/DialogInterface.java +++ b/core/java/android/content/DialogInterface.java @@ -92,6 +92,21 @@ public interface DialogInterface { } /** + * Interface used to allow the creator of a dialog to run some code when the + * dialog is shown. + * @hide Pending API council approval + */ + interface OnShowListener { + /** + * This method will be invoked when the dialog is shown. + * + * @param dialog The dialog that was shown will be passed into the + * method. + */ + public void onShow(DialogInterface dialog); + } + + /** * Interface used to allow the creator of a dialog to run some code when an * item on the dialog is clicked.. */ diff --git a/core/java/android/content/Intent.java b/core/java/android/content/Intent.java index 263f927..a5f298c 100644 --- a/core/java/android/content/Intent.java +++ b/core/java/android/content/Intent.java @@ -1112,11 +1112,17 @@ public class Intent implements Parcelable { /** * Broadcast Action: Sent after the screen turns off. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_SCREEN_OFF = "android.intent.action.SCREEN_OFF"; /** * Broadcast Action: Sent after the screen turns on. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_SCREEN_ON = "android.intent.action.SCREEN_ON"; @@ -1124,6 +1130,9 @@ public class Intent implements Parcelable { /** * Broadcast Action: Sent when the user is present after device wakes up (e.g when the * keyguard is gone). + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_USER_PRESENT= "android.intent.action.USER_PRESENT"; @@ -1134,6 +1143,9 @@ public class Intent implements Parcelable { * in manifests, only by exlicitly registering for it with * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) * Context.registerReceiver()}. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_TIME_TICK = "android.intent.action.TIME_TICK"; @@ -1152,6 +1164,9 @@ public class Intent implements Parcelable { * <ul> * <li><em>time-zone</em> - The java.util.TimeZone.getID() value identifying the new time zone.</li> * </ul> + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_TIMEZONE_CHANGED = "android.intent.action.TIMEZONE_CHANGED"; @@ -1177,6 +1192,9 @@ public class Intent implements Parcelable { * such as installing alarms. You must hold the * {@link android.Manifest.permission#RECEIVE_BOOT_COMPLETED} permission * in order to receive this broadcast. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_BOOT_COMPLETED = "android.intent.action.BOOT_COMPLETED"; @@ -1190,6 +1208,9 @@ public class Intent implements Parcelable { * Broadcast Action: Trigger the download and eventual installation * of a package. * <p>Input: {@link #getData} is the URI of the package file to download. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PACKAGE_INSTALL = "android.intent.action.PACKAGE_INSTALL"; @@ -1203,6 +1224,9 @@ public class Intent implements Parcelable { * <li> {@link #EXTRA_REPLACING} is set to true if this is following * an {@link #ACTION_PACKAGE_REMOVED} broadcast for the same package. * </ul> + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PACKAGE_ADDED = "android.intent.action.PACKAGE_ADDED"; @@ -1214,6 +1238,9 @@ public class Intent implements Parcelable { * <ul> * <li> {@link #EXTRA_UID} containing the integer uid assigned to the new package. * </ul> + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PACKAGE_REPLACED = "android.intent.action.PACKAGE_REPLACED"; @@ -1229,6 +1256,9 @@ public class Intent implements Parcelable { * <li> {@link #EXTRA_REPLACING} is set to true if this will be followed * by an {@link #ACTION_PACKAGE_ADDED} broadcast for the same package. * </ul> + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PACKAGE_REMOVED = "android.intent.action.PACKAGE_REMOVED"; @@ -1238,6 +1268,9 @@ public class Intent implements Parcelable { * <ul> * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. * </ul> + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PACKAGE_CHANGED = "android.intent.action.PACKAGE_CHANGED"; @@ -1251,6 +1284,9 @@ public class Intent implements Parcelable { * <ul> * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. * </ul> + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PACKAGE_RESTARTED = "android.intent.action.PACKAGE_RESTARTED"; @@ -1263,12 +1299,18 @@ public class Intent implements Parcelable { * <ul> * <li> {@link #EXTRA_UID} containing the integer uid assigned to the package. * </ul> + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_PACKAGE_DATA_CLEARED = "android.intent.action.PACKAGE_DATA_CLEARED"; /** * Broadcast Action: A user ID has been removed from the system. The user * ID number is stored in the extra data under {@link #EXTRA_UID}. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_UID_REMOVED = "android.intent.action.UID_REMOVED"; @@ -1287,6 +1329,9 @@ public class Intent implements Parcelable { * application to make sure it sees the new changes. Some system code that * can not be restarted will need to watch for this action and handle it * appropriately. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. * * @see android.content.res.Configuration */ @@ -1298,15 +1343,21 @@ public class Intent implements Parcelable { * * <p class="note"> * You can <em>not</em> receive this through components declared - * in manifests, only by exlicitly registering for it with + * in manifests, only by explicitly registering for it with * {@link Context#registerReceiver(BroadcastReceiver, IntentFilter) * Context.registerReceiver()}. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_BATTERY_CHANGED = "android.intent.action.BATTERY_CHANGED"; /** * Broadcast Action: Indicates low battery condition on the device. * This broadcast corresponds to the "Low battery warning" system dialog. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_BATTERY_LOW = "android.intent.action.BATTERY_LOW"; @@ -1314,6 +1365,9 @@ public class Intent implements Parcelable { * Broadcast Action: Indicates the battery is now okay after being low. * This will be sent after {@link #ACTION_BATTERY_LOW} once the battery has * gone back up to an okay state. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_BATTERY_OKAY = "android.intent.action.BATTERY_OKAY"; @@ -1323,6 +1377,9 @@ public class Intent implements Parcelable { * Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to * stay active to receive this notification. This action can be used to implement actions * that wait until power is available to trigger. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_POWER_CONNECTED = "android.intent.action.ACTION_POWER_CONNECTED"; @@ -1332,6 +1389,9 @@ public class Intent implements Parcelable { * Unlike ACTION_BATTERY_CHANGED, applications will be woken for this and so do not have to * stay active to receive this notification. This action can be used to implement actions * that wait until power is available to trigger. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_POWER_DISCONNECTED = "android.intent.action.ACTION_POWER_DISCONNECTED"; @@ -1341,16 +1401,25 @@ public class Intent implements Parcelable { * off, not sleeping). Once the broadcast is complete, the final shutdown * will proceed and all unsaved data lost. Apps will not normally need * to handle this, since the forground activity will be paused as well. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_SHUTDOWN = "android.intent.action.ACTION_SHUTDOWN"; /** * Broadcast Action: Indicates low memory condition on the device + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_DEVICE_STORAGE_LOW = "android.intent.action.DEVICE_STORAGE_LOW"; /** * Broadcast Action: Indicates low memory condition on the device no longer exists + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_DEVICE_STORAGE_OK = "android.intent.action.DEVICE_STORAGE_OK"; @@ -1515,6 +1584,9 @@ public class Intent implements Parcelable { * then cell radio and possibly other radios such as bluetooth or WiFi may have also been * turned off</li> * </ul> + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_AIRPLANE_MODE_CHANGED = "android.intent.action.AIRPLANE_MODE"; @@ -1593,6 +1665,9 @@ public class Intent implements Parcelable { * <p>You must hold the * {@link android.Manifest.permission#PROCESS_OUTGOING_CALLS} * permission to receive this Intent.</p> + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_NEW_OUTGOING_CALL = @@ -1601,14 +1676,54 @@ public class Intent implements Parcelable { /** * Broadcast Action: Have the device reboot. This is only for use by * system code. + * + * <p class="note">This is a protected intent that can only be sent + * by the system. */ @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) public static final String ACTION_REBOOT = "android.intent.action.REBOOT"; + /** + * Broadcast Action: Triggers the platform Text-To-Speech engine to + * start the activity that installs the resource files on the device + * that are required for TTS to be operational. Since the installation + * of the data can be interrupted or declined by the user, the application + * shouldn't expect successful installation upon return from that intent, + * and if need be, should check installation status with + * {@link #ACTION_TTS_CHECK_TTS_DATA}. + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_TTS_INSTALL_TTS_DATA = + "android.intent.action.INSTALL_TTS_DATA"; + + /** + * Broadcast Action: Starts the activity from the platform Text-To-Speech + * engine to verify the proper installation and availability of the + * resource files on the system. Upon completion, the activity will + * return one of the following codes: + * {@link android.speech.tts.TextToSpeech.Engine#CHECK_VOICE_DATA_PASS}, + * {@link android.speech.tts.TextToSpeech.Engine#CHECK_VOICE_DATA_FAIL}, + * {@link android.speech.tts.TextToSpeech.Engine#CHECK_VOICE_DATA_BAD_DATA}, + * {@link android.speech.tts.TextToSpeech.Engine#CHECK_VOICE_DATA_MISSING_DATA}, or + * {@link android.speech.tts.TextToSpeech.Engine#CHECK_VOICE_DATA_MISSING_VOLUME}. + * <p> Moreover, the data received in the activity result will contain the following + * fields: + * <ul> + * <li>{@link android.speech.tts.TextToSpeech.Engine#VOICE_DATA_ROOT_DIRECTORY} which + * indicates the path to the location of the resource files</li>, + * <li>{@link android.speech.tts.TextToSpeech.Engine#VOICE_DATA_FILES} which contains + * the list of all the resource files</li>, + * <li>and {@link android.speech.tts.TextToSpeech.Engine#VOICE_DATA_FILES_INFO} which + * contains, for each resource file, the description of the language covered by + * the file in the xxx-YYY format, where xxx is the 3-letter ISO language code, + * and YYY is the 3-letter ISO country code.</li> + * </ul> + */ + @SdkConstant(SdkConstantType.BROADCAST_INTENT_ACTION) + public static final String ACTION_TTS_CHECK_TTS_DATA = + "android.intent.action.CHECK_TTS_DATA"; /** - * @hide - * TODO: This will be unhidden in a later CL. * Broadcast Action: The TextToSpeech synthesizer has completed processing * all of the text in the speech queue. */ @@ -4398,6 +4513,9 @@ public class Intent implements Parcelable { * and {@link #FILL_IN_COMPONENT} to override the restriction where the * corresponding field will not be replaced if it is already set. * + * <p>Note: The component field will only be copied if {@link #FILL_IN_COMPONENT} is explicitly + * specified. + * * <p>For example, consider Intent A with {data="foo", categories="bar"} * and Intent B with {action="gotit", data-type="some/thing", * categories="one","two"}. diff --git a/core/java/android/content/SyncStorageEngine.java b/core/java/android/content/SyncStorageEngine.java index f781e0d..756f35c 100644 --- a/core/java/android/content/SyncStorageEngine.java +++ b/core/java/android/content/SyncStorageEngine.java @@ -338,6 +338,7 @@ public class SyncStorageEngine extends Handler { } reports.add(mChangeListeners.getBroadcastItem(i)); } + mChangeListeners.finishBroadcast(); } if (DEBUG) Log.v(TAG, "reportChange " + which + " to: " + reports); diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index bcf95b6..4a3137f 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -161,19 +161,27 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { public static final int FLAG_SUPPORTS_LARGE_SCREENS = 1<<11; /** + * Value for {@link #flags}: true when the application knows how to adjust + * its UI for different screen sizes. Corresponds to + * {@link android.R.styleable#AndroidManifestSupportsScreens_resizeable + * android:resizeable}. + */ + public static final int FLAG_RESIZEABLE_FOR_SCREENS = 1<<12; + + /** * Value for {@link #flags}: this is false if the application has set * its android:allowBackup to false, true otherwise. * * {@hide} */ - public static final int FLAG_ALLOW_BACKUP = 1<<12; + public static final int FLAG_ALLOW_BACKUP = 1<<13; /** * Indicates that the application supports any densities; * {@hide} */ public static final int ANY_DENSITY = -1; - private static final int[] ANY_DENSITIES_ARRAY = { ANY_DENSITY }; + static final int[] ANY_DENSITIES_ARRAY = { ANY_DENSITY }; /** * Flags associated with the application. Any combination of @@ -183,7 +191,7 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * {@link #FLAG_ALLOW_CLEAR_USER_DATA}, {@link #FLAG_UPDATED_SYSTEM_APP}, * {@link #FLAG_TEST_ONLY}, {@link #FLAG_SUPPORTS_SMALL_SCREENS}, * {@link #FLAG_SUPPORTS_NORMAL_SCREENS}, - * {@link #FLAG_SUPPORTS_LARGE_SCREENS}. + * {@link #FLAG_SUPPORTS_LARGE_SCREENS}, {@link #FLAG_RESIZEABLE_FOR_SCREENS}. */ public int flags = 0; @@ -399,7 +407,8 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * @hide */ public void disableCompatibilityMode() { - flags |= FLAG_SUPPORTS_LARGE_SCREENS; + flags |= (FLAG_SUPPORTS_LARGE_SCREENS | FLAG_SUPPORTS_NORMAL_SCREENS | + FLAG_SUPPORTS_SMALL_SCREENS | FLAG_RESIZEABLE_FOR_SCREENS); supportsDensities = ANY_DENSITIES_ARRAY; } } diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index bf2a895..e587ca7 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -71,6 +71,8 @@ interface IPackageManager { void removePermission(String name); + boolean isProtectedBroadcast(String actionName); + int checkSignatures(String pkg1, String pkg2); String[] getPackagesForUid(int uid); diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 558b0c3..93ba959 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -92,6 +92,8 @@ public class PackageParser { private static final Object mSync = new Object(); private static WeakReference<byte[]> mReadBuffer; + private static boolean sCompatibilityModeEnabled = true; + static class ParsePackageItemArgs { final Package owner; final String[] outError; @@ -672,6 +674,7 @@ public class PackageParser { int supportsSmallScreens = 1; int supportsNormalScreens = 1; int supportsLargeScreens = 1; + int resizeable = 1; int outerDepth = parser.getDepth(); while ((type=parser.next()) != parser.END_DOCUMENT @@ -720,7 +723,7 @@ public class PackageParser { sa.recycle(); if (name != null && !pkg.requestedPermissions.contains(name)) { - pkg.requestedPermissions.add(name); + pkg.requestedPermissions.add(name.intern()); } XmlUtils.skipCurrentTag(parser); @@ -851,21 +854,6 @@ public class PackageParser { XmlUtils.skipCurrentTag(parser); - } else if (tagName.equals("instrumentation")) { - if (parseInstrumentation(pkg, res, parser, attrs, outError) == null) { - return null; - } - } else if (tagName.equals("eat-comment")) { - // Just skip this tag - XmlUtils.skipCurrentTag(parser); - continue; - } else if (RIGID_PARSER) { - outError[0] = "Bad element under <manifest>: " - + parser.getName(); - mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; - return null; - - } else if (tagName.equals("supports-density")) { sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestSupportsDensity); @@ -896,10 +884,50 @@ public class PackageParser { supportsLargeScreens = sa.getInteger( com.android.internal.R.styleable.AndroidManifestSupportsScreens_largeScreens, supportsLargeScreens); + resizeable = sa.getInteger( + com.android.internal.R.styleable.AndroidManifestSupportsScreens_resizeable, + supportsLargeScreens); sa.recycle(); XmlUtils.skipCurrentTag(parser); + + } else if (tagName.equals("protected-broadcast")) { + sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AndroidManifestProtectedBroadcast); + + String name = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestProtectedBroadcast_name); + + sa.recycle(); + + if (name != null && (flags&PARSE_IS_SYSTEM) != 0) { + if (pkg.protectedBroadcasts == null) { + pkg.protectedBroadcasts = new ArrayList<String>(); + } + if (!pkg.protectedBroadcasts.contains(name)) { + pkg.protectedBroadcasts.add(name.intern()); + } + } + + XmlUtils.skipCurrentTag(parser); + + } else if (tagName.equals("instrumentation")) { + if (parseInstrumentation(pkg, res, parser, attrs, outError) == null) { + return null; + } + + } else if (tagName.equals("eat-comment")) { + // Just skip this tag + XmlUtils.skipCurrentTag(parser); + continue; + + } else if (RIGID_PARSER) { + outError[0] = "Bad element under <manifest>: " + + parser.getName(); + mParseError = PackageManager.INSTALL_PARSE_FAILED_MANIFEST_MALFORMED; + return null; + } else { Log.w(TAG, "Bad element under <manifest>: " + parser.getName()); @@ -945,15 +973,30 @@ public class PackageParser { >= android.os.Build.VERSION_CODES.CUR_DEVELOPMENT)) { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS; } - + if (resizeable < 0 || (resizeable > 0 + && pkg.applicationInfo.targetSdkVersion + >= android.os.Build.VERSION_CODES.CUR_DEVELOPMENT)) { + pkg.applicationInfo.flags |= ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS; + } + int densities[] = null; int size = pkg.supportsDensityList.size(); if (size > 0) { - int densities[] = pkg.supportsDensities = new int[size]; + densities = pkg.supportsDensities = new int[size]; List<Integer> densityList = pkg.supportsDensityList; for (int i = 0; i < size; i++) { densities[i] = densityList.get(i); } } + /** + * TODO: enable this before code freeze. b/1967935 + * * + if ((densities == null || densities.length == 0) + && (pkg.applicationInfo.targetSdkVersion + >= android.os.Build.VERSION_CODES.CUR_DEVELOPMENT)) { + pkg.supportsDensities = ApplicationInfo.ANY_DENSITIES_ARRAY; + } + */ + return pkg; } @@ -1419,7 +1462,7 @@ public class PackageParser { sa.recycle(); if (lname != null && !owner.usesLibraries.contains(lname)) { - owner.usesLibraries.add(lname); + owner.usesLibraries.add(lname.intern()); } XmlUtils.skipCurrentTag(parser); @@ -1908,6 +1951,7 @@ public class PackageParser { outInfo.metaData, outError)) == null) { return false; } + } else if (parser.getName().equals("grant-uri-permission")) { TypedArray sa = res.obtainAttributes(attrs, com.android.internal.R.styleable.AndroidManifestGrantUriPermission); @@ -1931,7 +1975,7 @@ public class PackageParser { if (str != null) { pa = new PatternMatcher(str, PatternMatcher.PATTERN_SIMPLE_GLOB); } - + sa.recycle(); if (pa != null) { @@ -1946,6 +1990,101 @@ public class PackageParser { outInfo.info.uriPermissionPatterns = newp; } outInfo.info.grantUriPermissions = true; + } else { + if (!RIGID_PARSER) { + Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":"); + Log.w(TAG, "No path, pathPrefix, or pathPattern for <path-permission>"); + XmlUtils.skipCurrentTag(parser); + continue; + } + outError[0] = "No path, pathPrefix, or pathPattern for <path-permission>"; + return false; + } + XmlUtils.skipCurrentTag(parser); + + } else if (parser.getName().equals("path-permission")) { + TypedArray sa = res.obtainAttributes(attrs, + com.android.internal.R.styleable.AndroidManifestPathPermission); + + PathPermission pa = null; + + String permission = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_permission); + String readPermission = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_readPermission); + if (readPermission == null) { + readPermission = permission; + } + String writePermission = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_writePermission); + if (writePermission == null) { + writePermission = permission; + } + + boolean havePerm = false; + if (readPermission != null) { + readPermission = readPermission.intern(); + havePerm = true; + } + if (writePermission != null) { + writePermission = readPermission.intern(); + havePerm = true; + } + + if (!havePerm) { + if (!RIGID_PARSER) { + Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":"); + Log.w(TAG, "No readPermission or writePermssion for <path-permission>"); + XmlUtils.skipCurrentTag(parser); + continue; + } + outError[0] = "No readPermission or writePermssion for <path-permission>"; + return false; + } + + String path = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_path); + if (path != null) { + pa = new PathPermission(path, + PatternMatcher.PATTERN_LITERAL, readPermission, writePermission); + } + + path = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_pathPrefix); + if (path != null) { + pa = new PathPermission(path, + PatternMatcher.PATTERN_PREFIX, readPermission, writePermission); + } + + path = sa.getNonResourceString( + com.android.internal.R.styleable.AndroidManifestPathPermission_pathPattern); + if (path != null) { + pa = new PathPermission(path, + PatternMatcher.PATTERN_SIMPLE_GLOB, readPermission, writePermission); + } + + sa.recycle(); + + if (pa != null) { + if (outInfo.info.pathPermissions == null) { + outInfo.info.pathPermissions = new PathPermission[1]; + outInfo.info.pathPermissions[0] = pa; + } else { + final int N = outInfo.info.pathPermissions.length; + PathPermission[] newp = new PathPermission[N+1]; + System.arraycopy(outInfo.info.pathPermissions, 0, newp, 0, N); + newp[N] = pa; + outInfo.info.pathPermissions = newp; + } + } else { + if (!RIGID_PARSER) { + Log.w(TAG, "Problem in package " + mArchiveSourcePath + ":"); + Log.w(TAG, "No path, pathPrefix, or pathPattern for <path-permission>"); + XmlUtils.skipCurrentTag(parser); + continue; + } + outError[0] = "No path, pathPrefix, or pathPattern for <path-permission>"; + return false; } XmlUtils.skipCurrentTag(parser); @@ -2104,8 +2243,8 @@ public class PackageParser { return null; } - boolean success = true; - + name = name.intern(); + TypedValue v = sa.peekValue( com.android.internal.R.styleable.AndroidManifestMetaData_resource); if (v != null && v.resourceId != 0) { @@ -2118,7 +2257,7 @@ public class PackageParser { if (v != null) { if (v.type == TypedValue.TYPE_STRING) { CharSequence cs = v.coerceToString(); - data.putString(name, cs != null ? cs.toString() : null); + data.putString(name, cs != null ? cs.toString().intern() : null); } else if (v.type == TypedValue.TYPE_INT_BOOLEAN) { data.putBoolean(name, v.data != 0); } else if (v.type >= TypedValue.TYPE_FIRST_INT @@ -2299,6 +2438,8 @@ public class PackageParser { public final ArrayList<String> requestedPermissions = new ArrayList<String>(); + public ArrayList<String> protectedBroadcasts; + public final ArrayList<String> usesLibraries = new ArrayList<String>(); public String[] usesLibraryFiles = null; @@ -2499,6 +2640,11 @@ public class PackageParser { public static ApplicationInfo generateApplicationInfo(Package p, int flags) { if (p == null) return null; if (!copyNeeded(flags, p, null)) { + // CompatibilityMode is global state. It's safe to modify the instance + // of the package. + if (!sCompatibilityModeEnabled) { + p.applicationInfo.disableCompatibilityMode(); + } return p.applicationInfo; } @@ -2513,6 +2659,9 @@ public class PackageParser { if ((flags & PackageManager.GET_SUPPORTS_DENSITIES) != 0) { ai.supportsDensities = p.supportsDensities; } + if (!sCompatibilityModeEnabled) { + ai.disableCompatibilityMode(); + } return ai; } @@ -2697,4 +2846,11 @@ public class PackageParser { + " " + service.info.name + "}"; } } + + /** + * @hide + */ + public static void setCompatibilityModeEnabled(boolean compatibilityModeEnabled) { + sCompatibilityModeEnabled = compatibilityModeEnabled; + } } diff --git a/core/java/android/content/pm/PathPermission.java b/core/java/android/content/pm/PathPermission.java new file mode 100644 index 0000000..7e49d7d --- /dev/null +++ b/core/java/android/content/pm/PathPermission.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2009 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.content.pm; + +import android.os.Parcel; +import android.os.Parcelable; +import android.os.PatternMatcher; + +/** + * Description of permissions needed to access a particular path + * in a {@link ProviderInfo}. + */ +public class PathPermission extends PatternMatcher { + private final String mReadPermission; + private final String mWritePermission; + + public PathPermission(String pattern, int type, String readPermission, + String writePermission) { + super(pattern, type); + mReadPermission = readPermission; + mWritePermission = writePermission; + } + + public String getReadPermission() { + return mReadPermission; + } + + public String getWritePermission() { + return mWritePermission; + } + + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + dest.writeString(mReadPermission); + dest.writeString(mWritePermission); + } + + public PathPermission(Parcel src) { + super(src); + mReadPermission = src.readString(); + mWritePermission = src.readString(); + } + + public static final Parcelable.Creator<PathPermission> CREATOR + = new Parcelable.Creator<PathPermission>() { + public PathPermission createFromParcel(Parcel source) { + return new PathPermission(source); + } + + public PathPermission[] newArray(int size) { + return new PathPermission[size]; + } + }; +}
\ No newline at end of file diff --git a/core/java/android/content/pm/ProviderInfo.java b/core/java/android/content/pm/ProviderInfo.java index b67ddf6..d01460e 100644 --- a/core/java/android/content/pm/ProviderInfo.java +++ b/core/java/android/content/pm/ProviderInfo.java @@ -28,6 +28,7 @@ import android.os.PatternMatcher; */ public final class ProviderInfo extends ComponentInfo implements Parcelable { + /** The name provider is published under content:// */ public String authority = null; @@ -56,6 +57,14 @@ public final class ProviderInfo extends ComponentInfo */ public PatternMatcher[] uriPermissionPatterns = null; + /** + * If non-null, these are path-specific permissions that are allowed for + * accessing the provider. Any permissions listed here will allow a + * holding client to access the provider, and the provider will check + * the URI it provides when making calls against the patterns here. + */ + public PathPermission[] pathPermissions = null; + /** If true, this content provider allows multiple instances of itself * to run in different process. If false, a single instances is always * run in {@link #processName}. */ @@ -78,6 +87,7 @@ public final class ProviderInfo extends ComponentInfo writePermission = orig.writePermission; grantUriPermissions = orig.grantUriPermissions; uriPermissionPatterns = orig.uriPermissionPatterns; + pathPermissions = orig.pathPermissions; multiprocess = orig.multiprocess; initOrder = orig.initOrder; isSyncable = orig.isSyncable; @@ -94,6 +104,7 @@ public final class ProviderInfo extends ComponentInfo out.writeString(writePermission); out.writeInt(grantUriPermissions ? 1 : 0); out.writeTypedArray(uriPermissionPatterns, parcelableFlags); + out.writeTypedArray(pathPermissions, parcelableFlags); out.writeInt(multiprocess ? 1 : 0); out.writeInt(initOrder); out.writeInt(isSyncable ? 1 : 0); @@ -122,6 +133,7 @@ public final class ProviderInfo extends ComponentInfo writePermission = in.readString(); grantUriPermissions = in.readInt() != 0; uriPermissionPatterns = in.createTypedArray(PatternMatcher.CREATOR); + pathPermissions = in.createTypedArray(PathPermission.CREATOR); multiprocess = in.readInt() != 0; initOrder = in.readInt(); isSyncable = in.readInt() != 0; diff --git a/core/java/android/content/res/CompatibilityInfo.java b/core/java/android/content/res/CompatibilityInfo.java index dfe304d..517551e 100644 --- a/core/java/android/content/res/CompatibilityInfo.java +++ b/core/java/android/content/res/CompatibilityInfo.java @@ -38,7 +38,12 @@ public class CompatibilityInfo { private static final String TAG = "CompatibilityInfo"; /** default compatibility info object for compatible applications */ - public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo(); + public static final CompatibilityInfo DEFAULT_COMPATIBILITY_INFO = new CompatibilityInfo() { + @Override + public void setExpandable(boolean expandable) { + throw new UnsupportedOperationException("trying to change default compatibility info"); + } + }; /** * The default width of the screen in portrait mode. @@ -51,18 +56,6 @@ public class CompatibilityInfo { public static final int DEFAULT_PORTRAIT_HEIGHT = 480; /** - * The x-shift mode that controls the position of the content or the window under - * compatibility mode. - * {@see getTranslator} - * {@see Translator#mShiftMode} - */ - private static final int X_SHIFT_NONE = 0; - private static final int X_SHIFT_CONTENT = 1; - private static final int X_SHIFT_AND_CLIP_CONTENT = 2; - private static final int X_SHIFT_WINDOW = 3; - - - /** * A compatibility flags */ private int mCompatibilityFlags; @@ -76,8 +69,8 @@ public class CompatibilityInfo { /** * A flag mask to indicates that the application can expand over the original size. * The flag is set to true if - * 1) Application declares its expandable in manifest file using <expandable /> or - * 2) The screen size is same as (320 x 480) * density. + * 1) Application declares its expandable in manifest file using <supports-screens> or + * 2) Configuration.SCREENLAYOUT_COMPAT_NEEDED is not set * {@see compatibilityFlag} */ private static final int EXPANDABLE = 2; @@ -85,13 +78,35 @@ public class CompatibilityInfo { /** * A flag mask to tell if the application is configured to be expandable. This differs * from EXPANDABLE in that the application that is not expandable will be - * marked as expandable if it runs in (320x 480) * density screen size. + * marked as expandable if Configuration.SCREENLAYOUT_COMPAT_NEEDED is not set. */ private static final int CONFIGURED_EXPANDABLE = 4; - private static final int SCALING_EXPANDABLE_MASK = SCALING_REQUIRED | EXPANDABLE; + /** + * A flag mask to indicates that the application supports large screens. + * The flag is set to true if + * 1) Application declares it supports large screens in manifest file using <supports-screens> or + * 2) The screen size is not large + * {@see compatibilityFlag} + */ + private static final int LARGE_SCREENS = 8; + + /** + * A flag mask to tell if the application supports large screens. This differs + * from LARGE_SCREENS in that the application that does not support large + * screens will be marked as supporting them if the current screen is not + * large. + */ + private static final int CONFIGURED_LARGE_SCREENS = 16; + + private static final int SCALING_EXPANDABLE_MASK = SCALING_REQUIRED | EXPANDABLE | LARGE_SCREENS; /** + * The effective screen density we have selected for this application. + */ + public final int applicationDensity; + + /** * Application's scale. */ public final float applicationScale; @@ -106,62 +121,60 @@ public class CompatibilityInfo { */ public final int appFlags; - /** - * Window size in Compatibility Mode, in real pixels. This is updated by - * {@link DisplayMetrics#updateMetrics}. - */ - private int mWidth; - private int mHeight; - - /** - * The x offset to center the window content. In X_SHIFT_WINDOW mode, the offset is added - * to the window's layout. In X_SHIFT_CONTENT/X_SHIFT_AND_CLIP_CONTENT mode, the offset - * is used to translate the Canvas. - */ - private int mXOffset; - public CompatibilityInfo(ApplicationInfo appInfo) { appFlags = appInfo.flags; if ((appInfo.flags & ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS) != 0) { - mCompatibilityFlags = EXPANDABLE | CONFIGURED_EXPANDABLE; + mCompatibilityFlags |= LARGE_SCREENS | CONFIGURED_LARGE_SCREENS; + } + if ((appInfo.flags & ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS) != 0) { + mCompatibilityFlags |= EXPANDABLE | CONFIGURED_EXPANDABLE; } float packageDensityScale = -1.0f; + int packageDensity = 0; if (appInfo.supportsDensities != null) { int minDiff = Integer.MAX_VALUE; for (int density : appInfo.supportsDensities) { - if (density == ApplicationInfo.ANY_DENSITY) { + if (density == ApplicationInfo.ANY_DENSITY) { + packageDensity = DisplayMetrics.DENSITY_DEVICE; packageDensityScale = 1.0f; break; } - int tmpDiff = Math.abs(DisplayMetrics.DEVICE_DENSITY - density); + int tmpDiff = Math.abs(DisplayMetrics.DENSITY_DEVICE - density); if (tmpDiff == 0) { + packageDensity = DisplayMetrics.DENSITY_DEVICE; packageDensityScale = 1.0f; break; } // prefer higher density (appScale>1.0), unless that's only option. if (tmpDiff < minDiff && packageDensityScale < 1.0f) { - packageDensityScale = DisplayMetrics.DEVICE_DENSITY / (float) density; + packageDensity = density; + packageDensityScale = DisplayMetrics.DENSITY_DEVICE / (float) density; minDiff = tmpDiff; } } } if (packageDensityScale > 0.0f) { + applicationDensity = packageDensity; applicationScale = packageDensityScale; } else { + applicationDensity = DisplayMetrics.DENSITY_DEFAULT; applicationScale = - DisplayMetrics.DEVICE_DENSITY / (float) DisplayMetrics.DEFAULT_DENSITY; + DisplayMetrics.DENSITY_DEVICE / (float) DisplayMetrics.DENSITY_DEFAULT; } + applicationInvertedScale = 1.0f / applicationScale; if (applicationScale != 1.0f) { mCompatibilityFlags |= SCALING_REQUIRED; } } - private CompatibilityInfo(int appFlags, int compFlags, float scale, float invertedScale) { + private CompatibilityInfo(int appFlags, int compFlags, + int dens, float scale, float invertedScale) { this.appFlags = appFlags; mCompatibilityFlags = compFlags; + applicationDensity = dens; applicationScale = scale; applicationInvertedScale = invertedScale; } @@ -169,8 +182,10 @@ public class CompatibilityInfo { private CompatibilityInfo() { this(ApplicationInfo.FLAG_SUPPORTS_SMALL_SCREENS | ApplicationInfo.FLAG_SUPPORTS_NORMAL_SCREENS - | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS, + | ApplicationInfo.FLAG_SUPPORTS_LARGE_SCREENS + | ApplicationInfo.FLAG_RESIZEABLE_FOR_SCREENS, EXPANDABLE | CONFIGURED_EXPANDABLE, + DisplayMetrics.DENSITY_DEVICE, 1.0f, 1.0f); } @@ -180,24 +195,11 @@ public class CompatibilityInfo { */ public CompatibilityInfo copy() { CompatibilityInfo info = new CompatibilityInfo(appFlags, mCompatibilityFlags, - applicationScale, applicationInvertedScale); - info.setVisibleRect(mXOffset, mWidth, mHeight); + applicationDensity, applicationScale, applicationInvertedScale); return info; } /** - * Sets the application's visible rect in compatibility mode. - * @param xOffset the application's x offset that is added to center the content. - * @param widthPixels the application's width in real pixels on the screen. - * @param heightPixels the application's height in real pixels on the screen. - */ - public void setVisibleRect(int xOffset, int widthPixels, int heightPixels) { - this.mXOffset = xOffset; - mWidth = widthPixels; - mHeight = heightPixels; - } - - /** * Sets expandable bit in the compatibility flag. */ public void setExpandable(boolean expandable) { @@ -209,6 +211,17 @@ public class CompatibilityInfo { } /** + * Sets large screen bit in the compatibility flag. + */ + public void setLargeScreens(boolean expandable) { + if (expandable) { + mCompatibilityFlags |= CompatibilityInfo.LARGE_SCREENS; + } else { + mCompatibilityFlags &= ~CompatibilityInfo.LARGE_SCREENS; + } + } + + /** * @return true if the application is configured to be expandable. */ public boolean isConfiguredExpandable() { @@ -216,73 +229,45 @@ public class CompatibilityInfo { } /** + * @return true if the application is configured to be expandable. + */ + public boolean isConfiguredLargeScreens() { + return (mCompatibilityFlags & CompatibilityInfo.CONFIGURED_LARGE_SCREENS) != 0; + } + + /** * @return true if the scaling is required */ public boolean isScalingRequired() { return (mCompatibilityFlags & SCALING_REQUIRED) != 0; } + public boolean supportsScreen() { + return (mCompatibilityFlags & (EXPANDABLE|LARGE_SCREENS)) + == (EXPANDABLE|LARGE_SCREENS); + } + @Override public String toString() { return "CompatibilityInfo{scale=" + applicationScale + - ", compatibility flag=" + mCompatibilityFlags + "}"; + ", supports screen=" + supportsScreen() + "}"; } /** * Returns the translator which can translate the coordinates of the window. * There are five different types of Translator. - * - * 1) {@link CompatibilityInfo#X_SHIFT_AND_CLIP_CONTENT} - * Shift and clip the content of the window at drawing time. Used for activities' - * main window (with no gravity). - * 2) {@link CompatibilityInfo#X_SHIFT_CONTENT} - * Shift the content of the window at drawing time. Used for windows that is created by - * an application and expected to be aligned with the application window. - * 3) {@link CompatibilityInfo#X_SHIFT_WINDOW} - * Create the window with adjusted x- coordinates. This is typically used - * in popup window, where it has to be placed relative to main window. - * 4) {@link CompatibilityInfo#X_SHIFT_NONE} - * No adjustment required, such as dialog. - * 5) Same as X_SHIFT_WINDOW, but no scaling. This is used by {@link SurfaceView}, which - * does not require scaling, but its window's location has to be adjusted. - * * @param params the window's parameter */ public Translator getTranslator(WindowManager.LayoutParams params) { - if ( (mCompatibilityFlags & CompatibilityInfo.SCALING_EXPANDABLE_MASK) - == CompatibilityInfo.EXPANDABLE) { + if ( (mCompatibilityFlags & SCALING_EXPANDABLE_MASK) + == (EXPANDABLE|LARGE_SCREENS)) { if (DBG) Log.d(TAG, "no translation required"); return null; } - - if ((mCompatibilityFlags & CompatibilityInfo.EXPANDABLE) == 0) { - if ((params.flags & WindowManager.LayoutParams.FLAG_NO_COMPATIBILITY_SCALING) != 0) { - if (DBG) Log.d(TAG, "translation for surface view selected"); - return new Translator(X_SHIFT_WINDOW, false, 1.0f, 1.0f); - } else { - int shiftMode; - if (params.gravity == Gravity.NO_GRAVITY) { - // For Regular Application window - shiftMode = X_SHIFT_AND_CLIP_CONTENT; - if (DBG) Log.d(TAG, "shift and clip translator"); - } else if (params.width == WindowManager.LayoutParams.FILL_PARENT) { - // For Regular Application window - shiftMode = X_SHIFT_CONTENT; - if (DBG) Log.d(TAG, "shift content translator"); - } else if ((params.gravity & Gravity.LEFT) != 0 && params.x > 0) { - shiftMode = X_SHIFT_WINDOW; - if (DBG) Log.d(TAG, "shift window translator"); - } else { - shiftMode = X_SHIFT_NONE; - if (DBG) Log.d(TAG, "no content/window translator"); - } - return new Translator(shiftMode); - } - } else if (isScalingRequired()) { - return new Translator(); - } else { + if (!isScalingRequired()) { return null; } + return new Translator(); } /** @@ -290,97 +275,48 @@ public class CompatibilityInfo { * @hide */ public class Translator { - final private int mShiftMode; - final public boolean scalingRequired; final public float applicationScale; final public float applicationInvertedScale; private Rect mContentInsetsBuffer = null; - private Rect mVisibleInsets = null; + private Rect mVisibleInsetsBuffer = null; - Translator(int shiftMode, boolean scalingRequired, float applicationScale, - float applicationInvertedScale) { - mShiftMode = shiftMode; - this.scalingRequired = scalingRequired; + Translator(float applicationScale, float applicationInvertedScale) { this.applicationScale = applicationScale; this.applicationInvertedScale = applicationInvertedScale; } - Translator(int shiftMode) { - this(shiftMode, - isScalingRequired(), - CompatibilityInfo.this.applicationScale, - CompatibilityInfo.this.applicationInvertedScale); - } - Translator() { - this(X_SHIFT_NONE); + this(CompatibilityInfo.this.applicationScale, + CompatibilityInfo.this.applicationInvertedScale); } /** * Translate the screen rect to the application frame. */ public void translateRectInScreenToAppWinFrame(Rect rect) { - if (rect.isEmpty()) return; // skip if the window size is empty. - switch (mShiftMode) { - case X_SHIFT_AND_CLIP_CONTENT: - rect.intersect(0, 0, mWidth, mHeight); - break; - case X_SHIFT_CONTENT: - rect.intersect(0, 0, mWidth + mXOffset, mHeight); - break; - case X_SHIFT_WINDOW: - case X_SHIFT_NONE: - break; - } - if (scalingRequired) { - rect.scale(applicationInvertedScale); - } + rect.scale(applicationInvertedScale); } /** * Translate the region in window to screen. */ public void translateRegionInWindowToScreen(Region transparentRegion) { - switch (mShiftMode) { - case X_SHIFT_AND_CLIP_CONTENT: - case X_SHIFT_CONTENT: - transparentRegion.scale(applicationScale); - transparentRegion.translate(mXOffset, 0); - break; - case X_SHIFT_WINDOW: - case X_SHIFT_NONE: - transparentRegion.scale(applicationScale); - } + transparentRegion.scale(applicationScale); } /** * Apply translation to the canvas that is necessary to draw the content. */ public void translateCanvas(Canvas canvas) { - if (mShiftMode == X_SHIFT_CONTENT || - mShiftMode == X_SHIFT_AND_CLIP_CONTENT) { - // TODO: clear outside when rotation is changed. - - // Translate x-offset only when the content is shifted. - canvas.translate(mXOffset, 0); - } - if (scalingRequired) { - canvas.scale(applicationScale, applicationScale); - } + canvas.scale(applicationScale, applicationScale); } /** * Translate the motion event captured on screen to the application's window. */ public void translateEventInScreenToAppWindow(MotionEvent event) { - if (mShiftMode == X_SHIFT_CONTENT || - mShiftMode == X_SHIFT_AND_CLIP_CONTENT) { - event.translate(-mXOffset, 0); - } - if (scalingRequired) { - event.scale(applicationInvertedScale); - } + event.scale(applicationInvertedScale); } /** @@ -388,62 +324,21 @@ public class CompatibilityInfo { * Screen's view. */ public void translateWindowLayout(WindowManager.LayoutParams params) { - switch (mShiftMode) { - case X_SHIFT_NONE: - case X_SHIFT_AND_CLIP_CONTENT: - case X_SHIFT_CONTENT: - params.scale(applicationScale); - break; - case X_SHIFT_WINDOW: - params.scale(applicationScale); - params.x += mXOffset; - break; - } + params.scale(applicationScale); } /** * Translate a Rect in application's window to screen. */ public void translateRectInAppWindowToScreen(Rect rect) { - // TODO Auto-generated method stub - if (scalingRequired) { - rect.scale(applicationScale); - } - switch(mShiftMode) { - case X_SHIFT_NONE: - case X_SHIFT_WINDOW: - break; - case X_SHIFT_CONTENT: - case X_SHIFT_AND_CLIP_CONTENT: - rect.offset(mXOffset, 0); - break; - } + rect.scale(applicationScale); } /** * Translate a Rect in screen coordinates into the app window's coordinates. */ public void translateRectInScreenToAppWindow(Rect rect) { - switch (mShiftMode) { - case X_SHIFT_NONE: - case X_SHIFT_WINDOW: - break; - case X_SHIFT_CONTENT: { - rect.intersects(mXOffset, 0, rect.right, rect.bottom); - int dx = Math.min(mXOffset, rect.left); - rect.offset(-dx, 0); - break; - } - case X_SHIFT_AND_CLIP_CONTENT: { - rect.intersects(mXOffset, 0, mWidth + mXOffset, mHeight); - int dx = Math.min(mXOffset, rect.left); - rect.offset(-dx, 0); - break; - } - } - if (scalingRequired) { - rect.scale(applicationInvertedScale); - } + rect.scale(applicationInvertedScale); } /** @@ -451,19 +346,7 @@ public class CompatibilityInfo { * @param params */ public void translateLayoutParamsInAppWindowToScreen(LayoutParams params) { - if (scalingRequired) { - params.scale(applicationScale); - } - switch (mShiftMode) { - // the window location on these mode does not require adjustmenet. - case X_SHIFT_NONE: - case X_SHIFT_WINDOW: - break; - case X_SHIFT_CONTENT: - case X_SHIFT_AND_CLIP_CONTENT: - params.x += mXOffset; - break; - } + params.scale(applicationScale); } /** @@ -482,10 +365,31 @@ public class CompatibilityInfo { * the internal buffer for content insets to avoid extra object allocation. */ public Rect getTranslatedVisbileInsets(Rect visibleInsets) { - if (mVisibleInsets == null) mVisibleInsets = new Rect(); - mVisibleInsets.set(visibleInsets); - translateRectInAppWindowToScreen(mVisibleInsets); - return mVisibleInsets; + if (mVisibleInsetsBuffer == null) mVisibleInsetsBuffer = new Rect(); + mVisibleInsetsBuffer.set(visibleInsets); + translateRectInAppWindowToScreen(mVisibleInsetsBuffer); + return mVisibleInsetsBuffer; + } + } + + /** + * Returns the frame Rect for applications runs under compatibility mode. + * + * @param dm the display metrics used to compute the frame size. + * @param orientation the orientation of the screen. + * @param outRect the output parameter which will contain the result. + */ + public static void updateCompatibleScreenFrame(DisplayMetrics dm, int orientation, + Rect outRect) { + int width = dm.widthPixels; + int portraitHeight = (int) (DEFAULT_PORTRAIT_HEIGHT * dm.density + 0.5f); + int portraitWidth = (int) (DEFAULT_PORTRAIT_WIDTH * dm.density + 0.5f); + if (orientation == Configuration.ORIENTATION_LANDSCAPE) { + int xOffset = (width - portraitHeight) / 2 ; + outRect.set(xOffset, 0, xOffset + portraitHeight, portraitWidth); + } else { + int xOffset = (width - portraitWidth) / 2 ; + outRect.set(xOffset, 0, xOffset + portraitWidth, portraitHeight); } } } diff --git a/core/java/android/content/res/Configuration.java b/core/java/android/content/res/Configuration.java index 577aa60..5f44cc9 100644 --- a/core/java/android/content/res/Configuration.java +++ b/core/java/android/content/res/Configuration.java @@ -41,6 +41,39 @@ public final class Configuration implements Parcelable, Comparable<Configuration */ public boolean userSetLocale; + public static final int SCREENLAYOUT_SIZE_MASK = 0x0f; + public static final int SCREENLAYOUT_SIZE_UNDEFINED = 0x00; + public static final int SCREENLAYOUT_SIZE_SMALL = 0x01; + public static final int SCREENLAYOUT_SIZE_NORMAL = 0x02; + public static final int SCREENLAYOUT_SIZE_LARGE = 0x03; + + public static final int SCREENLAYOUT_LONG_MASK = 0x30; + public static final int SCREENLAYOUT_LONG_UNDEFINED = 0x00; + public static final int SCREENLAYOUT_LONG_NO = 0x10; + public static final int SCREENLAYOUT_LONG_YES = 0x20; + + /** + * Special flag we generate to indicate that the screen layout requires + * us to use a compatibility mode for apps that are not modern layout + * aware. + * @hide + */ + public static final int SCREENLAYOUT_COMPAT_NEEDED = 0x10000000; + + /** + * Bit mask of overall layout of the screen. Currently there are two + * fields: + * <p>The {@link #SCREENLAYOUT_SIZE_MASK} bits define the overall size + * of the screen. They may be one of + * {@link #SCREENLAYOUT_SIZE_SMALL}, {@link #SCREENLAYOUT_SIZE_NORMAL}, + * or {@link #SCREENLAYOUT_SIZE_LARGE}. + * + * <p>The {@link #SCREENLAYOUT_LONG_MASK} defines whether the screen + * is wider/taller than normal. They may be one of + * {@link #SCREENLAYOUT_LONG_NO} or {@link #SCREENLAYOUT_LONG_YES}. + */ + public int screenLayout; + public static final int TOUCHSCREEN_UNDEFINED = 0; public static final int TOUCHSCREEN_NOTOUCH = 1; public static final int TOUCHSCREEN_STYLUS = 2; @@ -116,18 +149,6 @@ public final class Configuration implements Parcelable, Comparable<Configuration */ public int orientation; - public static final int SCREENLAYOUT_UNDEFINED = 0; - public static final int SCREENLAYOUT_SMALL = 1; - public static final int SCREENLAYOUT_NORMAL = 2; - public static final int SCREENLAYOUT_LARGE = 3; - - /** - * Overall layout of the screen. May be one of - * {@link #SCREENLAYOUT_SMALL}, {@link #SCREENLAYOUT_NORMAL}, - * or {@link #SCREENLAYOUT_LARGE}. - */ - public int screenLayout; - /** * Construct an invalid Configuration. You must call {@link #setToDefaults} * for this object to be valid. {@more} @@ -198,7 +219,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration hardKeyboardHidden = HARDKEYBOARDHIDDEN_UNDEFINED; navigation = NAVIGATION_UNDEFINED; orientation = ORIENTATION_UNDEFINED; - screenLayout = SCREENLAYOUT_UNDEFINED; + screenLayout = SCREENLAYOUT_SIZE_UNDEFINED; } /** {@hide} */ @@ -269,7 +290,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration changed |= ActivityInfo.CONFIG_ORIENTATION; orientation = delta.orientation; } - if (delta.screenLayout != SCREENLAYOUT_UNDEFINED + if (delta.screenLayout != SCREENLAYOUT_SIZE_UNDEFINED && screenLayout != delta.screenLayout) { changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT; screenLayout = delta.screenLayout; @@ -342,7 +363,7 @@ public final class Configuration implements Parcelable, Comparable<Configuration && orientation != delta.orientation) { changed |= ActivityInfo.CONFIG_ORIENTATION; } - if (delta.screenLayout != SCREENLAYOUT_UNDEFINED + if (delta.screenLayout != SCREENLAYOUT_SIZE_UNDEFINED && screenLayout != delta.screenLayout) { changed |= ActivityInfo.CONFIG_SCREEN_LAYOUT; } diff --git a/core/java/android/content/res/Resources.java b/core/java/android/content/res/Resources.java index 49ad656..2354519 100644 --- a/core/java/android/content/res/Resources.java +++ b/core/java/android/content/res/Resources.java @@ -34,6 +34,7 @@ import android.util.Log; import android.util.SparseArray; import android.util.TypedValue; import android.util.LongSparseArray; +import android.view.Display; import java.io.IOException; import java.io.InputStream; @@ -86,7 +87,8 @@ public class Resources { /*package*/ final DisplayMetrics mMetrics = new DisplayMetrics(); PluralRules mPluralRule; - private final CompatibilityInfo mCompatibilityInfo; + private CompatibilityInfo mCompatibilityInfo; + private Display mDefaultDisplay; private static final LongSparseArray<Object> EMPTY_ARRAY = new LongSparseArray<Object>() { @Override @@ -129,53 +131,30 @@ public class Resources { */ public Resources(AssetManager assets, DisplayMetrics metrics, Configuration config) { - this(assets, metrics, config, (ApplicationInfo) null); + this(assets, metrics, config, (CompatibilityInfo) null); } /** - * Creates a new Resources object with ApplicationInfo. + * Creates a new Resources object with CompatibilityInfo. * * @param assets Previously created AssetManager. * @param metrics Current display metrics to consider when * selecting/computing resource values. * @param config Desired device configuration to consider when * selecting/computing resource values (optional). - * @param appInfo this resource's application info. + * @param compInfo this resource's compatibility info. It will use the default compatibility + * info when it's null. * @hide */ public Resources(AssetManager assets, DisplayMetrics metrics, - Configuration config, ApplicationInfo appInfo) { + Configuration config, CompatibilityInfo compInfo) { mAssets = assets; - mConfiguration.setToDefaults(); mMetrics.setToDefaults(); - if (appInfo != null) { - mCompatibilityInfo = new CompatibilityInfo(appInfo); - if (DEBUG_CONFIG) { - Log.d(TAG, "compatibility for " + appInfo.packageName + " : " + mCompatibilityInfo); - } - } else { + if (compInfo == null) { mCompatibilityInfo = CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO; - } - updateConfiguration(config, metrics); - assets.ensureStringBlocks(); - if (mCompatibilityInfo.isScalingRequired()) { - mPreloadedDrawables = emptySparseArray(); } else { - mPreloadedDrawables = sPreloadedDrawables; + mCompatibilityInfo = compInfo; } - } - - /** - * Creates a new resources that uses the given compatibility info. Used to create - * a context for widgets using the container's compatibility info. - * {@see ApplicationContext#createPackageCotnext}. - * @hide - */ - public Resources(AssetManager assets, DisplayMetrics metrics, - Configuration config, CompatibilityInfo info) { - mAssets = assets; - mMetrics.setToDefaults(); - mCompatibilityInfo = info; updateConfiguration(config, metrics); assets.ensureStringBlocks(); if (mCompatibilityInfo.isScalingRequired()) { @@ -1407,6 +1386,15 @@ public class Resources { } /** + * This is just for testing. + * @hide + */ + public void setCompatibilityInfo(CompatibilityInfo ci) { + mCompatibilityInfo = ci; + updateConfiguration(mConfiguration, mMetrics); + } + + /** * Return a resource identifier for the given resource name. A fully * qualified resource name is of the form "package:type/entry". The first * two components (package and type) are optional if defType and @@ -1938,6 +1926,24 @@ public class Resources { + Integer.toHexString(id)); } + /** + * Returns the display adjusted for the Resources' metrics. + * @hide + */ + public Display getDefaultDisplay(Display defaultDisplay) { + if (mDefaultDisplay == null) { + if (!mCompatibilityInfo.isScalingRequired() && mCompatibilityInfo.supportsScreen()) { + // the app supports the display. just use the default one. + mDefaultDisplay = defaultDisplay; + } else { + // display needs adjustment. + mDefaultDisplay = Display.createMetricsBasedDisplay( + defaultDisplay.getDisplayId(), mMetrics); + } + } + return mDefaultDisplay; + } + private TypedArray getCachedStyledAttributes(int len) { synchronized (mTmpValue) { TypedArray attrs = mCachedStyledAttributes; diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 7d331dc..184d6dc 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -1412,8 +1412,9 @@ public class SQLiteDatabase extends SQLiteClosable { StringBuilder sql = new StringBuilder(120); sql.append("UPDATE "); if (algorithm != null) { - sql.append(" OR "); + sql.append("OR "); sql.append(algorithm.value()); + sql.append(" "); } sql.append(table); diff --git a/core/java/android/gesture/Gesture.java b/core/java/android/gesture/Gesture.java index 2262477..62330e1 100755 --- a/core/java/android/gesture/Gesture.java +++ b/core/java/android/gesture/Gesture.java @@ -31,6 +31,7 @@ import java.io.DataInputStream; import java.io.ByteArrayOutputStream; import java.io.ByteArrayInputStream; import java.util.ArrayList; +import java.util.concurrent.atomic.AtomicInteger; /** * A gesture can have a single or multiple strokes @@ -44,7 +45,7 @@ public class Gesture implements Parcelable { private static final boolean BITMAP_RENDERING_ANTIALIAS = true; private static final boolean BITMAP_RENDERING_DITHER = true; - private static int sGestureCount = 0; + private static final AtomicInteger sGestureCount = new AtomicInteger(0); private final RectF mBoundingBox = new RectF(); @@ -54,12 +55,7 @@ public class Gesture implements Parcelable { private final ArrayList<GestureStroke> mStrokes = new ArrayList<GestureStroke>(); public Gesture() { - mGestureID = GESTURE_ID_BASE + sGestureCount++; - } - - void recycle() { - mStrokes.clear(); - mBoundingBox.setEmpty(); + mGestureID = GESTURE_ID_BASE + sGestureCount.incrementAndGet(); } /** @@ -162,20 +158,6 @@ public class Gesture implements Parcelable { } /** - * draw the gesture - * - * @param canvas - */ - void draw(Canvas canvas, Paint paint) { - final ArrayList<GestureStroke> strokes = mStrokes; - final int count = strokes.size(); - - for (int i = 0; i < count; i++) { - strokes.get(i).draw(canvas, paint); - } - } - - /** * Create a bitmap of the gesture with a transparent background * * @param width width of the target bitmap diff --git a/core/java/android/gesture/package.html b/core/java/android/gesture/package.html index a54a017..32c44d2 100644 --- a/core/java/android/gesture/package.html +++ b/core/java/android/gesture/package.html @@ -1,5 +1,5 @@ <HTML> <BODY> -@hide +Provides classes to create, recognize, load and save gestures. </BODY> </HTML> diff --git a/core/java/android/net/Uri.java b/core/java/android/net/Uri.java index c23df21..421d013 100644 --- a/core/java/android/net/Uri.java +++ b/core/java/android/net/Uri.java @@ -105,6 +105,18 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { private static final String LOG = Uri.class.getSimpleName(); /** + * NOTE: EMPTY accesses this field during its own initialization, so this + * field *must* be initialized first, or else EMPTY will see a null value! + * + * Placeholder for strings which haven't been cached. This enables us + * to cache null. We intentionally create a new String instance so we can + * compare its identity and there is no chance we will confuse it with + * user data. + */ + @SuppressWarnings("RedundantStringConstructorCall") + private static final String NOT_CACHED = new String("NOT CACHED"); + + /** * The empty URI, equivalent to "". */ public static final Uri EMPTY = new HierarchicalUri(null, Part.NULL, @@ -350,15 +362,6 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { private final static int NOT_CALCULATED = -2; /** - * Placeholder for strings which haven't been cached. This enables us - * to cache null. We intentionally create a new String instance so we can - * compare its identity and there is no chance we will confuse it with - * user data. - */ - @SuppressWarnings("RedundantStringConstructorCall") - private static final String NOT_CACHED = new String("NOT CACHED"); - - /** * Error message presented when a user tries to treat an opaque URI as * hierarchical. */ @@ -1080,7 +1083,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { /** Used in parcelling. */ static final int TYPE_ID = 3; - private final String scheme; + private final String scheme; // can be null private final Part authority; private final PathPart path; private final Part query; @@ -1089,10 +1092,10 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { private HierarchicalUri(String scheme, Part authority, PathPart path, Part query, Part fragment) { this.scheme = scheme; - this.authority = authority; - this.path = path; - this.query = query; - this.fragment = fragment; + this.authority = Part.nonNull(authority); + this.path = path == null ? PathPart.NULL : path; + this.query = Part.nonNull(query); + this.fragment = Part.nonNull(fragment); } static Uri readFrom(Parcel parcel) { @@ -1155,21 +1158,18 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { } private void appendSspTo(StringBuilder builder) { - if (authority != null) { - String encodedAuthority = authority.getEncoded(); - if (encodedAuthority != null) { - // Even if the authority is "", we still want to append "//". - builder.append("//").append(encodedAuthority); - } + String encodedAuthority = authority.getEncoded(); + if (encodedAuthority != null) { + // Even if the authority is "", we still want to append "//". + builder.append("//").append(encodedAuthority); } - // path is never null. String encodedPath = path.getEncoded(); if (encodedPath != null) { builder.append(encodedPath); } - if (query != null && !query.isEmpty()) { + if (!query.isEmpty()) { builder.append('?').append(query.getEncoded()); } } @@ -1229,7 +1229,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { appendSspTo(builder); - if (fragment != null && !fragment.isEmpty()) { + if (!fragment.isEmpty()) { builder.append('#').append(fragment.getEncoded()); } @@ -1503,7 +1503,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { throw new UnsupportedOperationException(NOT_HIERARCHICAL); } - String query = getQuery(); + String query = getEncodedQuery(); if (query == null) { return Collections.emptyList(); } @@ -1566,7 +1566,7 @@ public abstract class Uri implements Parcelable, Comparable<Uri> { throw new UnsupportedOperationException(NOT_HIERARCHICAL); } - String query = getQuery(); + String query = getEncodedQuery(); if (query == null) { return null; diff --git a/core/java/android/os/BatteryStats.java b/core/java/android/os/BatteryStats.java index 528def5..e203fd5 100644 --- a/core/java/android/os/BatteryStats.java +++ b/core/java/android/os/BatteryStats.java @@ -508,6 +508,19 @@ public abstract class BatteryStats implements Parcelable { public abstract long getBatteryUptime(long curTime); /** + * @deprecated use getRadioDataUptime + */ + public long getRadioDataUptimeMs() { + return getRadioDataUptime() / 1000; + } + + /** + * Returns the time that the radio was on for data transfers. + * @return the uptime in microseconds while unplugged + */ + public abstract long getRadioDataUptime(); + + /** * Returns the current battery realtime in microseconds. * * @param curTime the amount of elapsed realtime in microseconds. @@ -1128,7 +1141,14 @@ public abstract class BatteryStats implements Parcelable { } if (!didOne) sb.append("No activity"); pw.println(sb.toString()); - + + sb.setLength(0); + sb.append(prefix); + sb.append(" Radio data uptime when unplugged: "); + sb.append(getRadioDataUptime() / 1000); + sb.append(" ms"); + pw.println(sb.toString()); + sb.setLength(0); sb.append(prefix); sb.append(" Wifi on: "); formatTimeMs(sb, wifiOnTime / 1000); diff --git a/core/java/android/os/Process.java b/core/java/android/os/Process.java index 1214abc..4805193 100644 --- a/core/java/android/os/Process.java +++ b/core/java/android/os/Process.java @@ -68,6 +68,18 @@ public class Process { public static final int PHONE_UID = 1001; /** + * Defines the UID/GID for the user shell. + * @hide + */ + public static final int SHELL_UID = 2000; + + /** + * Defines the UID/GID for the WIFI supplicant process. + * @hide + */ + public static final int WIFI_UID = 1010; + + /** * Defines the start of a range of UIDs (and GIDs), going from this * number to {@link #LAST_APPLICATION_UID} that are reserved for assigning * to applications. diff --git a/core/java/android/os/RemoteCallbackList.java b/core/java/android/os/RemoteCallbackList.java index 23c0a7b..584224f 100644 --- a/core/java/android/os/RemoteCallbackList.java +++ b/core/java/android/os/RemoteCallbackList.java @@ -50,6 +50,7 @@ public class RemoteCallbackList<E extends IInterface> { /*package*/ HashMap<IBinder, Callback> mCallbacks = new HashMap<IBinder, Callback>(); private Object[] mActiveBroadcast; + private int mBroadcastCount = -1; private boolean mKilled = false; private final class Callback implements IBinder.DeathRecipient { @@ -195,15 +196,16 @@ public class RemoteCallbackList<E extends IInterface> { * This creates a copy of the callback list, which you can retrieve items * from using {@link #getBroadcastItem}. Note that only one broadcast can * be active at a time, so you must be sure to always call this from the - * same thread (usually by scheduling with {@link Handler} or + * same thread (usually by scheduling with {@link Handler}) or * do your own synchronization. You must call {@link #finishBroadcast} * when done. * * <p>A typical loop delivering a broadcast looks like this: * * <pre> - * final int N = callbacks.beginBroadcast(); - * for (int i=0; i<N; i++) { + * int i = callbacks.beginBroadcast(); + * while (i > 0) { + * i--; * try { * callbacks.getBroadcastItem(i).somethingHappened(); * } catch (RemoteException e) { @@ -222,7 +224,12 @@ public class RemoteCallbackList<E extends IInterface> { */ public int beginBroadcast() { synchronized (mCallbacks) { - final int N = mCallbacks.size(); + if (mBroadcastCount > 0) { + throw new IllegalStateException( + "beginBroadcast() called while already in a broadcast"); + } + + final int N = mBroadcastCount = mCallbacks.size(); if (N <= 0) { return 0; } @@ -281,12 +288,19 @@ public class RemoteCallbackList<E extends IInterface> { * @see #beginBroadcast */ public void finishBroadcast() { + if (mBroadcastCount < 0) { + throw new IllegalStateException( + "finishBroadcast() called outside of a broadcast"); + } + Object[] active = mActiveBroadcast; if (active != null) { - final int N = active.length; + final int N = mBroadcastCount; for (int i=0; i<N; i++) { active[i] = null; } } + + mBroadcastCount = -1; } } diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index aa583ac..b5440f2 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -1328,7 +1328,7 @@ public final class Settings { * boolean (1 or 0). */ public static final String HAPTIC_FEEDBACK_ENABLED = "haptic_feedback_enabled"; - + /** * Whether live web suggestions while the user types into search dialogs are * enabled. Browsers and other search UIs should respect this, as it allows @@ -2300,7 +2300,7 @@ public final class Settings { * @hide */ public static final String BACKUP_TRANSPORT = "backup_transport"; - + /** * Version for which the setup wizard was last shown. Bumped for * each release when there is new setup information to show. @@ -2508,11 +2508,17 @@ public final class Settings { public static final String CHECKIN_EVENTS = "checkin_events"; /** - * Event tags for list of services to upload during checkin. + * Comma-separated list of service names to dump and upload during checkin. */ public static final String CHECKIN_DUMPSYS_LIST = "checkin_dumpsys_list"; /** + * Comma-separated list of packages to specify for each service that is + * dumped (currently only meaningful for user activity). + */ + public static final String CHECKIN_PACKAGE_LIST = "checkin_package_list"; + + /** * The interval (in seconds) between periodic checkin attempts. */ public static final String CHECKIN_INTERVAL = "checkin_interval"; @@ -2692,6 +2698,12 @@ public final class Settings { public static final String GMAIL_NUM_RETRY_UPHILL_OP = "gmail_discard_error_uphill_op"; /** + * Controls if the protocol buffer version of the protocol will use a multipart request for + * attachment uploads. Value must be an integer where non-zero means true. Defaults to 0. + */ + public static final String GMAIL_USE_MULTIPART_PROTOBUF = "gmail_use_multipart_protobuf"; + + /** * the transcoder URL for mobile devices. */ public static final String TRANSCODER_URL = "mobile_transcoder_url"; @@ -2954,6 +2966,13 @@ public final class Settings { "vending_pd_resend_frequency_ms"; /** + * Frequency in milliseconds at which we should cycle through the promoted applications + * on the home screen or the categories page. + */ + public static final String VENDING_PROMO_REFRESH_FREQUENCY_MS = + "vending_promo_refresh_freq_ms"; + + /** * URL that points to the legal terms of service to display in Settings. * <p> * This should be a https URL. For a pretty user-friendly URL, use diff --git a/core/java/android/provider/Telephony.java b/core/java/android/provider/Telephony.java index 4078fa6..c292c53 100644 --- a/core/java/android/provider/Telephony.java +++ b/core/java/android/provider/Telephony.java @@ -44,7 +44,7 @@ import java.util.regex.Pattern; */ public final class Telephony { private static final String TAG = "Telephony"; - private static final boolean DEBUG = false; + private static final boolean DEBUG = true; private static final boolean LOCAL_LOGV = DEBUG ? Config.LOGD : Config.LOGV; // Constructor @@ -241,7 +241,7 @@ public final class Telephony { if (uri == null) { return false; } - + boolean markAsUnread = false; boolean markAsRead = false; switch(folder) { @@ -268,7 +268,7 @@ public final class Telephony { } else if (markAsRead) { values.put(READ, Integer.valueOf(1)); } - + return 1 == SqliteWrapper.update(context, context.getContentResolver(), uri, values, null, null); } @@ -1136,8 +1136,14 @@ public final class Telephony { } Uri uri = uriBuilder.build(); + if (DEBUG) { + Log.v(TAG, "getOrCreateThreadId uri: " + uri); + } 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()) { @@ -1620,6 +1626,9 @@ public final class Telephony { * * It is recommended to display <em>plmn</em> before / above <em>spn</em> if * both are displayed. + * + * <p>Note this is a protected intent that can only be sent + * by the system. */ public static final String SPN_STRINGS_UPDATED_ACTION = "android.provider.Telephony.SPN_STRINGS_UPDATED"; diff --git a/core/java/android/server/search/SearchDialogWrapper.java b/core/java/android/server/search/SearchDialogWrapper.java new file mode 100644 index 0000000..d3ef5de --- /dev/null +++ b/core/java/android/server/search/SearchDialogWrapper.java @@ -0,0 +1,387 @@ +/* + * 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.server.search; + +import android.app.ISearchManagerCallback; +import android.app.SearchDialog; +import android.app.SearchManager; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.IntentFilter; +import android.os.Bundle; +import android.os.DeadObjectException; +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.text.TextUtils; +import android.util.Log; + +/** + * Runs an instance of {@link SearchDialog} on its own thread. + */ +class SearchDialogWrapper +implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener { + + private static final String TAG = "SearchManagerService"; + private static final boolean DBG = false; + + private static final String SEARCH_UI_THREAD_NAME = "SearchDialog"; + private static final int SEARCH_UI_THREAD_PRIORITY = + android.os.Process.THREAD_PRIORITY_DEFAULT; + + // Takes no arguments + private static final int MSG_INIT = 0; + // Takes these arguments: + // arg1: selectInitialQuery, 0 = false, 1 = true + // arg2: globalSearch, 0 = false, 1 = true + // obj: searchManagerCallback + // data[KEY_INITIAL_QUERY]: initial query + // data[KEY_LAUNCH_ACTIVITY]: launch activity + // data[KEY_APP_SEARCH_DATA]: app search data + private static final int MSG_START_SEARCH = 1; + // Takes no arguments + private static final int MSG_STOP_SEARCH = 2; + // arg1 is activity id + private static final int MSG_ACTIVITY_RESUMING = 3; + + private static final String KEY_INITIAL_QUERY = "q"; + private static final String KEY_LAUNCH_ACTIVITY = "a"; + private static final String KEY_APP_SEARCH_DATA = "d"; + private static final String KEY_IDENT= "i"; + + // Context used for getting search UI resources + private final Context mContext; + + // Handles messages on the search UI thread. + private final SearchDialogHandler mSearchUiThread; + + // The search UI + SearchDialog mSearchDialog; + + // If the search UI is visible, this is the callback for the client that showed it. + ISearchManagerCallback mCallback = null; + + // Identity of last activity that started search. + private int mStartedIdent = 0; + + // Identity of currently resumed activity. + private int mResumedIdent = 0; + + // True if we have registered our receivers. + private boolean mReceiverRegistered; + + private volatile boolean mVisible = false; + + /** + * Creates a new search dialog wrapper and a search UI thread. The search dialog itself will + * be created some asynchronously on the search UI thread. + * + * @param context Context used for getting search UI resources. + */ + public SearchDialogWrapper(Context context) { + mContext = context; + + // Create the search UI thread + HandlerThread t = new HandlerThread(SEARCH_UI_THREAD_NAME, SEARCH_UI_THREAD_PRIORITY); + t.start(); + mSearchUiThread = new SearchDialogHandler(t.getLooper()); + + // Create search UI on the search UI thread + mSearchUiThread.sendEmptyMessage(MSG_INIT); + } + + public boolean isVisible() { + return mVisible; + } + + /** + * Initializes the search UI. + * Must be called from the search UI thread. + */ + private void init() { + mSearchDialog = new SearchDialog(mContext); + mSearchDialog.setOnCancelListener(this); + mSearchDialog.setOnDismissListener(this); + } + + private void registerBroadcastReceiver() { + if (!mReceiverRegistered) { + IntentFilter filter = new IntentFilter( + Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + filter.addAction(Intent.ACTION_CONFIGURATION_CHANGED); + mContext.registerReceiver(mBroadcastReceiver, filter, null, + mSearchUiThread); + mReceiverRegistered = true; + } + } + + private void unregisterBroadcastReceiver() { + if (mReceiverRegistered) { + mContext.unregisterReceiver(mBroadcastReceiver); + mReceiverRegistered = false; + } + } + + /** + * Closes the search dialog when requested by the system (e.g. when a phone call comes in). + */ + private BroadcastReceiver mBroadcastReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_CLOSE_SYSTEM_DIALOGS.equals(action)) { + if (!"search".equals(intent.getStringExtra("reason"))) { + if (DBG) debug(Intent.ACTION_CLOSE_SYSTEM_DIALOGS); + performStopSearch(); + } + } else if (Intent.ACTION_CONFIGURATION_CHANGED.equals(action)) { + if (DBG) debug(Intent.ACTION_CONFIGURATION_CHANGED); + performOnConfigurationChanged(); + } + } + }; + + // + // External API + // + + /** + * Launches the search UI. + * Can be called from any thread. + * + * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean) + */ + public void startSearch(final String initialQuery, + final boolean selectInitialQuery, + final ComponentName launchActivity, + final Bundle appSearchData, + final boolean globalSearch, + final ISearchManagerCallback searchManagerCallback, + int ident) { + if (DBG) debug("startSearch()"); + Message msg = Message.obtain(); + msg.what = MSG_START_SEARCH; + msg.arg1 = selectInitialQuery ? 1 : 0; + msg.arg2 = globalSearch ? 1 : 0; + msg.obj = searchManagerCallback; + Bundle msgData = msg.getData(); + msgData.putString(KEY_INITIAL_QUERY, initialQuery); + msgData.putParcelable(KEY_LAUNCH_ACTIVITY, launchActivity); + msgData.putBundle(KEY_APP_SEARCH_DATA, appSearchData); + msgData.putInt(KEY_IDENT, ident); + mSearchUiThread.sendMessage(msg); + } + + /** + * Cancels the search dialog. + * Can be called from any thread. + */ + public void stopSearch() { + if (DBG) debug("stopSearch()"); + mSearchUiThread.sendEmptyMessage(MSG_STOP_SEARCH); + } + + /** + * Updates the currently resumed activity. + * Can be called from any thread. + */ + public void activityResuming(int ident) { + if (DBG) debug("activityResuming(ident=" + ident + ")"); + Message msg = Message.obtain(); + msg.what = MSG_ACTIVITY_RESUMING; + msg.arg1 = ident; + mSearchUiThread.sendMessage(msg); + } + + // + // Implementation methods that run on the search UI thread + // + + private class SearchDialogHandler extends Handler { + + public SearchDialogHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_INIT: + init(); + break; + case MSG_START_SEARCH: + handleStartSearchMessage(msg); + break; + case MSG_STOP_SEARCH: + performStopSearch(); + break; + case MSG_ACTIVITY_RESUMING: + performActivityResuming(msg.arg1); + break; + } + } + + private void handleStartSearchMessage(Message msg) { + Bundle msgData = msg.getData(); + String initialQuery = msgData.getString(KEY_INITIAL_QUERY); + boolean selectInitialQuery = msg.arg1 != 0; + ComponentName launchActivity = + (ComponentName) msgData.getParcelable(KEY_LAUNCH_ACTIVITY); + Bundle appSearchData = msgData.getBundle(KEY_APP_SEARCH_DATA); + boolean globalSearch = msg.arg2 != 0; + ISearchManagerCallback searchManagerCallback = (ISearchManagerCallback) msg.obj; + int ident = msgData.getInt(KEY_IDENT); + performStartSearch(initialQuery, selectInitialQuery, launchActivity, + appSearchData, globalSearch, searchManagerCallback, ident); + } + + } + + /** + * Actually launches the search UI. + * This must be called on the search UI thread. + */ + void performStartSearch(String initialQuery, + boolean selectInitialQuery, + ComponentName launchActivity, + Bundle appSearchData, + boolean globalSearch, + ISearchManagerCallback searchManagerCallback, + int ident) { + if (DBG) debug("performStartSearch()"); + + registerBroadcastReceiver(); + mCallback = searchManagerCallback; + + // clean up any hidden dialog that we were waiting to resume + if (mStartedIdent != 0) { + mSearchDialog.dismiss(); + } + + mStartedIdent = ident; + if (DBG) Log.v(TAG, "******************* DIALOG: start"); + + mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData, + globalSearch); + mVisible = true; + } + + /** + * Actually cancels the search UI. + * This must be called on the search UI thread. + */ + void performStopSearch() { + if (DBG) debug("performStopSearch()"); + if (DBG) Log.v(TAG, "******************* DIALOG: cancel"); + mSearchDialog.cancel(); + mVisible = false; + mStartedIdent = 0; + } + + /** + * Updates the resumed activity + * This must be called on the search UI thread. + */ + void performActivityResuming(int ident) { + if (DBG) debug("performResumingActivity(): mStartedIdent=" + + mStartedIdent + ", resuming: " + ident); + this.mResumedIdent = ident; + if (mStartedIdent != 0) { + if (mStartedIdent == mResumedIdent) { + // we are resuming into the activity where we previously hid the dialog, bring it + // back + if (DBG) Log.v(TAG, "******************* DIALOG: show"); + mSearchDialog.show(); + mVisible = true; + } else { + // resuming into some other activity; hide ourselves in case we ever come back + // so we can show ourselves quickly again + if (DBG) Log.v(TAG, "******************* DIALOG: hide"); + mSearchDialog.hide(); + mVisible = false; + } + } + } + + /** + * Must be called from the search UI thread. + */ + void performOnConfigurationChanged() { + if (DBG) debug("performOnConfigurationChanged()"); + mSearchDialog.onConfigurationChanged(); + } + + /** + * Called by {@link SearchDialog} when it goes away. + */ + public void onDismiss(DialogInterface dialog) { + if (DBG) debug("onDismiss()"); + mStartedIdent = 0; + mVisible = false; + callOnDismiss(); + + // we don't need the callback anymore, release it + mCallback = null; + unregisterBroadcastReceiver(); + } + + + /** + * Called by {@link SearchDialog} when the user or activity cancels search. + * Whenever this method is called, {@link #onDismiss} is always called afterwards. + */ + public void onCancel(DialogInterface dialog) { + if (DBG) debug("onCancel()"); + callOnCancel(); + } + + private void callOnDismiss() { + if (mCallback == null) return; + try { + // should be safe to do on the search UI thread, since it's a oneway interface + mCallback.onDismiss(); + } catch (DeadObjectException ex) { + // The process that hosted the callback has died, do nothing + } catch (RemoteException ex) { + Log.e(TAG, "onDismiss() failed: " + ex); + } + } + + private void callOnCancel() { + if (mCallback != null) { + try { + // should be safe to do on the search UI thread, since it's a oneway interface + mCallback.onCancel(); + } catch (DeadObjectException ex) { + // The process that hosted the callback has died, do nothing + } catch (RemoteException ex) { + Log.e(TAG, "onCancel() failed: " + ex); + } + } + } + + private static void debug(String msg) { + Thread thread = Thread.currentThread(); + Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")"); + } +} diff --git a/core/java/android/server/search/SearchManagerService.java b/core/java/android/server/search/SearchManagerService.java index 373e61f..fdeb8f9 100644 --- a/core/java/android/server/search/SearchManagerService.java +++ b/core/java/android/server/search/SearchManagerService.java @@ -16,54 +16,43 @@ package android.server.search; +import android.app.ActivityManagerNative; +import android.app.IActivityWatcher; import android.app.ISearchManager; import android.app.ISearchManagerCallback; -import android.app.SearchDialog; import android.app.SearchManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; import android.content.IntentFilter; -import android.content.res.Configuration; import android.os.Bundle; import android.os.Handler; import android.os.RemoteException; -import android.os.SystemProperties; -import android.text.TextUtils; import android.util.Log; import java.util.List; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.FutureTask; /** - * This is a simplified version of the Search Manager service. It no longer handles - * presentation (UI). Its function is to maintain the map & list of "searchable" - * items, which provides a mapping from individual activities (where a user might have - * invoked search) to specific searchable activities (where the search will be dispatched). + * The search manager service handles the search UI, and maintains a registry of searchable + * activities. */ -public class SearchManagerService extends ISearchManager.Stub - implements DialogInterface.OnCancelListener, DialogInterface.OnDismissListener -{ - // general debugging support +public class SearchManagerService extends ISearchManager.Stub { + + // general debugging support private static final String TAG = "SearchManagerService"; private static final boolean DBG = false; - // class maintenance and general shared data + // Context that the service is running in. private final Context mContext; - private final Handler mHandler; - private boolean mSearchablesDirty; - private final Searchables mSearchables; - - final SearchDialog mSearchDialog; - ISearchManagerCallback mCallback = null; - private final boolean mDisabledOnBoot; + // This field is initialized in ensureSearchablesCreated(), and then never modified. + // Only accessed by ensureSearchablesCreated() and getSearchables() + private Searchables mSearchables; - private static final String DISABLE_SEARCH_PROPERTY = "dev.disablesearchdialog"; + // This field is initialized in ensureSearchDialogCreated(), and then never modified. + // Only accessed by ensureSearchDialogCreated() and getSearchDialog() + private SearchDialogWrapper mSearchDialog; /** * Initializes the Search Manager service in the provided system context. @@ -73,82 +62,98 @@ public class SearchManagerService extends ISearchManager.Stub */ public SearchManagerService(Context context) { mContext = context; - mHandler = new Handler(); - mSearchablesDirty = true; - mSearchables = new Searchables(context); - mSearchDialog = new SearchDialog(context); - mSearchDialog.setOnCancelListener(this); - mSearchDialog.setOnDismissListener(this); + // call initialize() after all pending actions on the main system thread have finished + new Handler().post(new Runnable() { + public void run() { + initialize(); + } + }); + } - // Setup the infrastructure for updating and maintaining the list - // of searchable activities. - IntentFilter filter = new IntentFilter(); - filter.addAction(Intent.ACTION_PACKAGE_ADDED); - filter.addAction(Intent.ACTION_PACKAGE_REMOVED); - filter.addAction(Intent.ACTION_PACKAGE_CHANGED); - filter.addDataScheme("package"); - mContext.registerReceiver(mIntentReceiver, filter, null, mHandler); + /** + * Initializes the list of searchable activities and the search UI. + */ + void initialize() { + try { + ActivityManagerNative.getDefault().registerActivityWatcher( + mActivityWatcher); + } catch (RemoteException e) { + } + } - // After startup settles down, preload the searchables list, - // which will reduce the delay when the search UI is invoked. - mHandler.post(mRunUpdateSearchable); + private synchronized void ensureSearchablesCreated() { + if (mSearchables != null) return; // already created - // allows disabling of search dialog for stress testing runs - mDisabledOnBoot = !TextUtils.isEmpty(SystemProperties.get(DISABLE_SEARCH_PROPERTY)); + mSearchables = new Searchables(mContext); + mSearchables.buildSearchableList(); + + IntentFilter packageFilter = new IntentFilter(); + packageFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + packageFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + packageFilter.addAction(Intent.ACTION_PACKAGE_CHANGED); + packageFilter.addDataScheme("package"); + mContext.registerReceiver(mPackageChangedReceiver, packageFilter); + } + + private synchronized void ensureSearchDialogCreated() { + if (mSearchDialog != null) return; + + mSearchDialog = new SearchDialogWrapper(mContext); + } + + private synchronized Searchables getSearchables() { + ensureSearchablesCreated(); + return mSearchables; + } + + private synchronized SearchDialogWrapper getSearchDialog() { + ensureSearchDialogCreated(); + return mSearchDialog; } /** - * Listens for intent broadcasts. - * - * The primary purpose here is to refresh the "searchables" list - * if packages are added/removed. + * Refreshes the "searchables" list when packages are added/removed. */ - private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { + private BroadcastReceiver mPackageChangedReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); - // First, test for intents that matter at any time - if (action.equals(Intent.ACTION_PACKAGE_ADDED) || - action.equals(Intent.ACTION_PACKAGE_REMOVED) || - action.equals(Intent.ACTION_PACKAGE_CHANGED)) { - mSearchablesDirty = true; - mHandler.post(mRunUpdateSearchable); - return; + if (Intent.ACTION_PACKAGE_ADDED.equals(action) || + Intent.ACTION_PACKAGE_REMOVED.equals(action) || + Intent.ACTION_PACKAGE_CHANGED.equals(action)) { + if (DBG) Log.d(TAG, "Got " + action); + // Dismiss search dialog, since the search context may no longer be valid + getSearchDialog().stopSearch(); + // Update list of searchable activities + getSearchables().buildSearchableList(); + broadcastSearchablesChanged(); } } }; - /** - * This runnable (for the main handler / UI thread) will update the searchables list. - */ - private Runnable mRunUpdateSearchable = new Runnable() { - public void run() { - updateSearchablesIfDirty(); + private IActivityWatcher.Stub mActivityWatcher = new IActivityWatcher.Stub() { + public void activityResuming(int activityId) throws RemoteException { + if (DBG) Log.i("foo", "********************** resuming: " + activityId); + if (mSearchDialog == null) return; + mSearchDialog.activityResuming(activityId); } }; - + /** - * Updates the list of searchables, either at startup or in response to - * a package add/remove broadcast message. + * Informs all listeners that the list of searchables has been updated. */ - private void updateSearchables() { - if (DBG) debug("updateSearchables()"); - mSearchables.buildSearchableList(); - mSearchablesDirty = false; + void broadcastSearchablesChanged() { + mContext.sendBroadcast( + new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED)); } - /** - * Updates the list of searchables if needed. - */ - private void updateSearchablesIfDirty() { - if (mSearchablesDirty) { - updateSearchables(); - } - } + // + // Searchable activities API + // /** - * Returns the SearchableInfo for a given activity + * Returns the SearchableInfo for a given activity. * * @param launchActivity The activity from which we're launching this search. * @param globalSearch If false, this will only launch the search that has been specifically @@ -158,226 +163,83 @@ public class SearchManagerService extends ISearchManager.Stub * @return Returns a SearchableInfo record describing the parameters of the search, * or null if no searchable metadata was available. */ - public SearchableInfo getSearchableInfo(ComponentName launchActivity, boolean globalSearch) { - updateSearchablesIfDirty(); - SearchableInfo si = null; + public SearchableInfo getSearchableInfo(final ComponentName launchActivity, + final boolean globalSearch) { if (globalSearch) { - si = mSearchables.getDefaultSearchable(); + return getSearchables().getDefaultSearchable(); } else { if (launchActivity == null) { Log.e(TAG, "getSearchableInfo(), activity == null"); return null; } - si = mSearchables.getSearchableInfo(launchActivity); + return getSearchables().getSearchableInfo(launchActivity); } - - return si; } /** * Returns a list of the searchable activities that can be included in global search. */ public List<SearchableInfo> getSearchablesInGlobalSearch() { - updateSearchablesIfDirty(); - return mSearchables.getSearchablesInGlobalSearchList(); - } - /** - * Launches the search UI on the main thread of the service. - * - * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean) - */ - public void startSearch(final String initialQuery, - final boolean selectInitialQuery, - final ComponentName launchActivity, - final Bundle appSearchData, - final boolean globalSearch, - final ISearchManagerCallback searchManagerCallback) { - if (DBG) debug("startSearch()"); - Runnable task = new Runnable() { - public void run() { - performStartSearch(initialQuery, - selectInitialQuery, - launchActivity, - appSearchData, - globalSearch, - searchManagerCallback); - } - }; - mHandler.post(task); - } - - /** - * Actually launches the search. This must be called on the service UI thread. - */ - /*package*/ void performStartSearch(String initialQuery, - boolean selectInitialQuery, - ComponentName launchActivity, - Bundle appSearchData, - boolean globalSearch, - ISearchManagerCallback searchManagerCallback) { - if (DBG) debug("performStartSearch()"); - - if (mDisabledOnBoot) { - Log.d(TAG, "ignoring start search request because " + DISABLE_SEARCH_PROPERTY - + " system property is set."); - return; - } - - mSearchDialog.show(initialQuery, selectInitialQuery, launchActivity, appSearchData, - globalSearch); - if (searchManagerCallback != null) { - mCallback = searchManagerCallback; - } - } - - /** - * Cancels the search dialog. Can be called from any thread. - */ - public void stopSearch() { - if (DBG) debug("stopSearch()"); - mHandler.post(new Runnable() { - public void run() { - performStopSearch(); - } - }); - } - - /** - * Cancels the search dialog. Must be called from the service UI thread. - */ - /*package*/ void performStopSearch() { - if (DBG) debug("performStopSearch()"); - mSearchDialog.cancel(); - } - - /** - * Determines if the Search UI is currently displayed. - * - * @see SearchManager#isVisible() - */ - public boolean isVisible() { - return postAndWait(mIsShowing, false, "isShowing()"); - } - - private final Callable<Boolean> mIsShowing = new Callable<Boolean>() { - public Boolean call() { - return mSearchDialog.isShowing(); - } - }; - - public Bundle onSaveInstanceState() { - return postAndWait(mOnSaveInstanceState, null, "onSaveInstanceState()"); - } - - private final Callable<Bundle> mOnSaveInstanceState = new Callable<Bundle>() { - public Bundle call() { - if (mSearchDialog.isShowing()) { - return mSearchDialog.onSaveInstanceState(); - } else { - return null; - } - } - }; - - public void onRestoreInstanceState(final Bundle searchDialogState) { - if (searchDialogState != null) { - mHandler.post(new Runnable() { - public void run() { - mSearchDialog.onRestoreInstanceState(searchDialogState); - } - }); - } - } - - public void onConfigurationChanged(final Configuration newConfig) { - mHandler.post(new Runnable() { - public void run() { - if (mSearchDialog.isShowing()) { - mSearchDialog.onConfigurationChanged(newConfig); - } - } - }); - } - - /** - * Called by {@link SearchDialog} when it goes away. - */ - public void onDismiss(DialogInterface dialog) { - if (DBG) debug("onDismiss()"); - if (mCallback != null) { - try { - mCallback.onDismiss(); - } catch (RemoteException ex) { - Log.e(TAG, "onDismiss() failed: " + ex); - } - } - } - - /** - * Called by {@link SearchDialog} when the user or activity cancels search. - * When this is called, {@link #onDismiss} is called too. - */ - public void onCancel(DialogInterface dialog) { - if (DBG) debug("onCancel()"); - if (mCallback != null) { - try { - mCallback.onCancel(); - } catch (RemoteException ex) { - Log.e(TAG, "onCancel() failed: " + ex); - } - } + return getSearchables().getSearchablesInGlobalSearchList(); } /** * Returns a list of the searchable activities that handle web searches. + * Can be called from any thread. */ public List<SearchableInfo> getSearchablesForWebSearch() { - updateSearchablesIfDirty(); - return mSearchables.getSearchablesForWebSearchList(); + return getSearchables().getSearchablesForWebSearchList(); } /** * Returns the default searchable activity for web searches. + * Can be called from any thread. */ public SearchableInfo getDefaultSearchableForWebSearch() { - updateSearchablesIfDirty(); - return mSearchables.getDefaultSearchableForWebSearch(); + return getSearchables().getDefaultSearchableForWebSearch(); } /** * Sets the default searchable activity for web searches. + * Can be called from any thread. */ - public void setDefaultWebSearch(ComponentName component) { - mSearchables.setDefaultWebSearch(component); + public void setDefaultWebSearch(final ComponentName component) { + getSearchables().setDefaultWebSearch(component); + broadcastSearchablesChanged(); } + // Search UI API + /** - * Runs an operation on the handler for the service, blocks until it returns, - * and returns the value returned by the operation. + * Launches the search UI. Can be called from any thread. * - * @param <V> Return value type. - * @param callable Operation to run. - * @param errorResult Value to return if the operations throws an exception. - * @param name Operation name to include in error log messages. - * @return The value returned by the operation. + * @see SearchManager#startSearch(String, boolean, ComponentName, Bundle, boolean) */ - private <V> V postAndWait(Callable<V> callable, V errorResult, String name) { - FutureTask<V> task = new FutureTask<V>(callable); - mHandler.post(task); - try { - return task.get(); - } catch (InterruptedException ex) { - Log.e(TAG, "Error calling " + name + ": " + ex); - return errorResult; - } catch (ExecutionException ex) { - Log.e(TAG, "Error calling " + name + ": " + ex); - return errorResult; - } + public void startSearch(String initialQuery, + boolean selectInitialQuery, + ComponentName launchActivity, + Bundle appSearchData, + boolean globalSearch, + ISearchManagerCallback searchManagerCallback, + int ident) { + getSearchDialog().startSearch(initialQuery, + selectInitialQuery, + launchActivity, + appSearchData, + globalSearch, + searchManagerCallback, + ident); } - private static void debug(String msg) { - Thread thread = Thread.currentThread(); - Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")"); + /** + * Cancels the search dialog. Can be called from any thread. + */ + public void stopSearch() { + getSearchDialog().stopSearch(); + } + + public boolean isVisible() { + return mSearchDialog != null && mSearchDialog.isVisible(); } } diff --git a/core/java/android/server/search/SearchableInfo.java b/core/java/android/server/search/SearchableInfo.java index 8ef1f15..045b0c2 100644 --- a/core/java/android/server/search/SearchableInfo.java +++ b/core/java/android/server/search/SearchableInfo.java @@ -67,6 +67,7 @@ public final class SearchableInfo implements Parcelable { private final int mSearchImeOptions; private final boolean mIncludeInGlobalSearch; private final boolean mQueryAfterZeroResults; + private final boolean mAutoUrlDetect; private final String mSettingsDescription; private final String mSuggestAuthority; private final String mSuggestPath; @@ -288,6 +289,8 @@ public final class SearchableInfo implements Parcelable { com.android.internal.R.styleable.Searchable_includeInGlobalSearch, false); mQueryAfterZeroResults = a.getBoolean( com.android.internal.R.styleable.Searchable_queryAfterZeroResults, false); + mAutoUrlDetect = a.getBoolean( + com.android.internal.R.styleable.Searchable_autoUrlDetect, false); mSettingsDescription = a.getString( com.android.internal.R.styleable.Searchable_searchSettingsDescription); @@ -439,6 +442,14 @@ public final class SearchableInfo implements Parcelable { mActionKeys.put(keyInfo.getKeyCode(), keyInfo); } + /** + * Gets search information for the given activity. + * + * @param context Context to use for reading activity resources. + * @param activityInfo Activity to get search information from. + * @return Search information about the given activity, or {@code null} if + * the activity has no or invalid searchability meta-data. + */ public static SearchableInfo getActivityMetaData(Context context, ActivityInfo activityInfo) { // for each component, try to find metadata XmlResourceParser xml = @@ -667,6 +678,16 @@ public final class SearchableInfo implements Parcelable { } /** + * Checks whether this searchable activity has auto URL detect turned on. + * + * @return The value of the <code>autoUrlDetect</code> attribute, + * or <code>false</code> if the attribute is not set. + */ + public boolean autoUrlDetect() { + return mAutoUrlDetect; + } + + /** * Support for parcelable and aidl operations. */ public static final Parcelable.Creator<SearchableInfo> CREATOR @@ -698,6 +719,7 @@ public final class SearchableInfo implements Parcelable { mSearchImeOptions = in.readInt(); mIncludeInGlobalSearch = in.readInt() != 0; mQueryAfterZeroResults = in.readInt() != 0; + mAutoUrlDetect = in.readInt() != 0; mSettingsDescription = in.readString(); mSuggestAuthority = in.readString(); @@ -735,6 +757,7 @@ public final class SearchableInfo implements Parcelable { dest.writeInt(mSearchImeOptions); dest.writeInt(mIncludeInGlobalSearch ? 1 : 0); dest.writeInt(mQueryAfterZeroResults ? 1 : 0); + dest.writeInt(mAutoUrlDetect ? 1 : 0); dest.writeString(mSettingsDescription); dest.writeString(mSuggestAuthority); diff --git a/core/java/android/server/search/Searchables.java b/core/java/android/server/search/Searchables.java index c7cc8ed..c615957 100644 --- a/core/java/android/server/search/Searchables.java +++ b/core/java/android/server/search/Searchables.java @@ -17,7 +17,6 @@ package android.server.search; import com.android.internal.app.ResolverActivity; -import com.android.internal.R; import android.app.SearchManager; import android.content.ComponentName; @@ -27,7 +26,6 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; -import android.content.res.Resources; import android.os.Bundle; import android.util.Log; @@ -249,7 +247,12 @@ public class Searchables { for (int i = 0; i < webSearchInfoList.size(); ++i) { ActivityInfo ai = webSearchInfoList.get(i).activityInfo; ComponentName component = new ComponentName(ai.packageName, ai.name); - newSearchablesForWebSearchList.add(newSearchablesMap.get(component)); + SearchableInfo searchable = newSearchablesMap.get(component); + if (searchable == null) { + Log.w(LOG_TAG, "did not find component in searchables: " + component); + } else { + newSearchablesForWebSearchList.add(searchable); + } } } @@ -264,7 +267,7 @@ public class Searchables { } // Find the default web search provider. - ComponentName webSearchActivity = getPreferredWebSearchActivity(); + ComponentName webSearchActivity = getPreferredWebSearchActivity(mContext); SearchableInfo newDefaultSearchableForWebSearch = null; if (webSearchActivity != null) { newDefaultSearchableForWebSearch = newSearchablesMap.get(webSearchActivity); @@ -283,9 +286,6 @@ public class Searchables { mDefaultSearchable = newDefaultSearchable; mDefaultSearchableForWebSearch = newDefaultSearchableForWebSearch; } - - // Inform all listeners that the list of searchables has been updated. - mContext.sendBroadcast(new Intent(SearchManager.INTENT_ACTION_SEARCHABLES_CHANGED)); } /** @@ -295,9 +295,10 @@ public class Searchables { * @param action Intent action for which this activity is to be set as preferred. * @return true if component was detected and set as preferred activity, false if not. */ - private boolean setPreferredActivity(ComponentName component, String action) { + private static boolean setPreferredActivity(Context context, + ComponentName component, String action) { Log.d(LOG_TAG, "Checking component " + component); - PackageManager pm = mContext.getPackageManager(); + PackageManager pm = context.getPackageManager(); ActivityInfo ai; try { ai = pm.getActivityInfo(component, 0); @@ -326,10 +327,10 @@ public class Searchables { return true; } - public ComponentName getPreferredWebSearchActivity() { + private static ComponentName getPreferredWebSearchActivity(Context context) { // Check if we have a preferred web search activity. Intent intent = new Intent(Intent.ACTION_WEB_SEARCH); - PackageManager pm = mContext.getPackageManager(); + PackageManager pm = context.getPackageManager(); ResolveInfo ri = pm.resolveActivity(intent, PackageManager.MATCH_DEFAULT_ONLY); if (ri == null || ri.activityInfo.name.equals(ResolverActivity.class.getName())) { @@ -338,11 +339,11 @@ public class Searchables { // The components in the providers array are checked in the order of declaration so the // first one has the highest priority. If the component exists in the system it is set // as the preferred activity to handle intent action web search. - String[] preferredActivities = mContext.getResources().getStringArray( + String[] preferredActivities = context.getResources().getStringArray( com.android.internal.R.array.default_web_search_providers); for (String componentName : preferredActivities) { ComponentName component = ComponentName.unflattenFromString(componentName); - if (setPreferredActivity(component, Intent.ACTION_WEB_SEARCH)) { + if (setPreferredActivity(context, component, Intent.ACTION_WEB_SEARCH)) { return component; } } @@ -354,7 +355,8 @@ public class Searchables { if (cn.flattenToShortString().equals(GOOGLE_SEARCH_COMPONENT_NAME)) { ComponentName enhancedGoogleSearch = ComponentName.unflattenFromString( ENHANCED_GOOGLE_SEARCH_COMPONENT_NAME); - if (setPreferredActivity(enhancedGoogleSearch, Intent.ACTION_WEB_SEARCH)) { + if (setPreferredActivity(context, enhancedGoogleSearch, + Intent.ACTION_WEB_SEARCH)) { return enhancedGoogleSearch; } } @@ -397,7 +399,7 @@ public class Searchables { * Sets the default searchable activity for web searches. */ public synchronized void setDefaultWebSearch(ComponentName component) { - setPreferredActivity(component, Intent.ACTION_WEB_SEARCH); + setPreferredActivity(mContext, component, Intent.ACTION_WEB_SEARCH); buildSearchableList(); } } diff --git a/core/java/android/speech/tts/ITts.aidl b/core/java/android/speech/tts/ITts.aidl index c9a6180..1812188 100755 --- a/core/java/android/speech/tts/ITts.aidl +++ b/core/java/android/speech/tts/ITts.aidl @@ -27,37 +27,37 @@ import android.content.Intent; * {@hide}
*/
interface ITts {
- int setSpeechRate(in int speechRate);
+ int setSpeechRate(in String callingApp, in int speechRate);
- int setPitch(in int pitch);
+ int setPitch(in String callingApp, in int pitch);
- int speak(in String text, in int queueMode, in String[] params);
+ int speak(in String callingApp, in String text, in int queueMode, in String[] params);
boolean isSpeaking();
- int stop();
+ int stop(in String callingApp);
- void addSpeech(in String text, in String packageName, in int resId);
+ void addSpeech(in String callingApp, in String text, in String packageName, in int resId);
- void addSpeechFile(in String text, in String filename);
+ void addSpeechFile(in String callingApp, in String text, in String filename);
String[] getLanguage();
int isLanguageAvailable(in String language, in String country, in String variant);
- int setLanguage(in String language, in String country, in String variant);
+ int setLanguage(in String callingApp, in String language, in String country, in String variant);
- boolean synthesizeToFile(in String text, in String[] params, in String outputDirectory);
+ boolean synthesizeToFile(in String callingApp, in String text, in String[] params, in String outputDirectory);
- int playEarcon(in String earcon, in int queueMode, in String[] params);
+ int playEarcon(in String callingApp, in String earcon, in int queueMode, in String[] params);
- void addEarcon(in String earcon, in String packageName, in int resId);
+ void addEarcon(in String callingApp, in String earcon, in String packageName, in int resId);
- void addEarconFile(in String earcon, in String filename);
+ void addEarconFile(in String callingApp, in String earcon, in String filename);
- void registerCallback(ITtsCallback cb);
+ int registerCallback(in String callingApp, ITtsCallback cb);
- void unregisterCallback(ITtsCallback cb);
+ int unregisterCallback(in String callingApp, ITtsCallback cb);
- int playSilence(in long duration, in int queueMode, in String[] params);
+ int playSilence(in String callingApp, in long duration, in int queueMode, in String[] params);
}
diff --git a/core/java/android/speech/tts/ITtsCallback.aidl b/core/java/android/speech/tts/ITtsCallback.aidl index 48ed73e..c9898eb 100755 --- a/core/java/android/speech/tts/ITtsCallback.aidl +++ b/core/java/android/speech/tts/ITtsCallback.aidl @@ -23,5 +23,5 @@ package android.speech.tts; * {@hide} */ oneway interface ITtsCallback { - void markReached(String mark); + void utteranceCompleted(String utteranceId); } diff --git a/core/java/android/speech/tts/TextToSpeech.java b/core/java/android/speech/tts/TextToSpeech.java index 616b3f1..bb6b4b0 100644..100755 --- a/core/java/android/speech/tts/TextToSpeech.java +++ b/core/java/android/speech/tts/TextToSpeech.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; +import android.media.AudioManager; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -98,40 +99,146 @@ public class TextToSpeech { } /** + * Called when the TTS has completed saying something that has an utterance ID set. + * + * The OnUtteranceCompletedListener must implement the onUtteranceCompleted function. + * onUtteranceCompleted is passed a String that is the utteranceId given in + * the original speak call. + */ + public interface OnUtteranceCompletedListener {
+ public void onUtteranceCompleted(String utteranceId);
+ } + + + /** * Internal constants for the TTS functionality * - * {@hide} */ public class Engine { // default values for a TTS engine when settings are not found in the provider + /** + * {@hide} + */ public static final int FALLBACK_TTS_DEFAULT_RATE = 100; // 1x + /** + * {@hide} + */ public static final int FALLBACK_TTS_DEFAULT_PITCH = 100;// 1x + /** + * {@hide} + */ public static final int FALLBACK_TTS_USE_DEFAULTS = 0; // false - public static final String FALLBACK_TTS_DEFAULT_LANG = "eng"; - public static final String FALLBACK_TTS_DEFAULT_COUNTRY = ""; - public static final String FALLBACK_TTS_DEFAULT_VARIANT = ""; + /** + * {@hide} + */ + public static final String FALLBACK_TTS_DEFAULT_SYNTH = "com.svox.pico"; + + // default values for rendering + public static final int TTS_DEFAULT_STREAM = AudioManager.STREAM_MUSIC; // return codes for a TTS engine's check data activity + /** + * Indicates success when checking the installation status of the resources used by the + * text-to-speech engine with the android.intent.action.CHECK_TTS_DATA intent. + */ public static final int CHECK_VOICE_DATA_PASS = 1; + /** + * Indicates failure when checking the installation status of the resources used by the + * text-to-speech engine with the android.intent.action.CHECK_TTS_DATA intent. + */ public static final int CHECK_VOICE_DATA_FAIL = 0; + /** + * Indicates erroneous data when checking the installation status of the resources used by + * the text-to-speech engine with the android.intent.action.CHECK_TTS_DATA intent. + */ public static final int CHECK_VOICE_DATA_BAD_DATA = -1; + /** + * Indicates missing resources when checking the installation status of the resources used + * by the text-to-speech engine with the android.intent.action.CHECK_TTS_DATA intent. + */ public static final int CHECK_VOICE_DATA_MISSING_DATA = -2; - public static final int CHECK_VOICE_DATA_MISSING_DATA_NO_SDCARD = -3; + /** + * Indicates missing storage volume when checking the installation status of the resources + * used by the text-to-speech engine with the android.intent.action.CHECK_TTS_DATA intent. + */ + public static final int CHECK_VOICE_DATA_MISSING_VOLUME = -3; // return codes for a TTS engine's check data activity + /** + * Extra information received with the android.intent.action.CHECK_TTS_DATA intent where + * the text-to-speech engine specifies the path to its resources. + */ public static final String VOICE_DATA_ROOT_DIRECTORY = "dataRoot"; + /** + * Extra information received with the android.intent.action.CHECK_TTS_DATA intent where + * the text-to-speech engine specifies the file names of its resources under the + * resource path. + */ public static final String VOICE_DATA_FILES = "dataFiles"; + /** + * Extra information received with the android.intent.action.CHECK_TTS_DATA intent where + * the text-to-speech engine specifies the locale associated with each resource file. + */ public static final String VOICE_DATA_FILES_INFO = "dataFilesInfo"; - // keys for the parameters passed with speak commands + // keys for the parameters passed with speak commands. Hidden keys are used internally + // to maintain engine state for each TextToSpeech instance. + /** + * {@hide} + */ public static final String TTS_KEY_PARAM_RATE = "rate"; + /** + * {@hide} + */ public static final String TTS_KEY_PARAM_LANGUAGE = "language"; + /** + * {@hide} + */ public static final String TTS_KEY_PARAM_COUNTRY = "country"; + /** + * {@hide} + */ public static final String TTS_KEY_PARAM_VARIANT = "variant"; - public static final int TTS_PARAM_POSITION_RATE = 0; - public static final int TTS_PARAM_POSITION_LANGUAGE = 2; - public static final int TTS_PARAM_POSITION_COUNTRY = 4; - public static final int TTS_PARAM_POSITION_VARIANT = 6; + /** + * Parameter key to specify the audio stream type to be used when speaking text + * or playing back a file. + */ + public static final String TTS_KEY_PARAM_STREAM = "streamType"; + /** + * Parameter key to identify an utterance in the completion listener after text has been + * spoken, a file has been played back or a silence duration has elapsed. + */ + public static final String TTS_KEY_PARAM_UTTERANCE_ID = "utteranceId"; + + // key positions in the array of cached parameters + /** + * {@hide} + */ + protected static final int TTS_PARAM_POSITION_RATE = 0; + /** + * {@hide} + */ + protected static final int TTS_PARAM_POSITION_LANGUAGE = 2; + /** + * {@hide} + */ + protected static final int TTS_PARAM_POSITION_COUNTRY = 4; + /** + * {@hide} + */ + protected static final int TTS_PARAM_POSITION_VARIANT = 6; + /** + * {@hide} + */ + protected static final int TTS_PARAM_POSITION_STREAM = 8; + /** + * {@hide} + */ + protected static final int TTS_PARAM_POSITION_UTTERANCE_ID = 10; + /** + * {@hide} + */ + protected static final int TTS_NB_CACHED_PARAMS = 6; } /** @@ -140,7 +247,9 @@ public class TextToSpeech { private ServiceConnection mServiceConnection; private ITts mITts = null; + private ITtsCallback mITtscallback = null; private Context mContext = null; + private String mPackageName = ""; private OnInitListener mInitListener = null; private boolean mStarted = false; private final Object mStartLock = new Object(); @@ -161,13 +270,16 @@ public class TextToSpeech { */ public TextToSpeech(Context context, OnInitListener listener) { mContext = context; + mPackageName = mContext.getPackageName(); mInitListener = listener; - mCachedParams = new String[2*4]; // 4 parameters, store key and value + mCachedParams = new String[2*Engine.TTS_NB_CACHED_PARAMS]; // store key and value mCachedParams[Engine.TTS_PARAM_POSITION_RATE] = Engine.TTS_KEY_PARAM_RATE; mCachedParams[Engine.TTS_PARAM_POSITION_LANGUAGE] = Engine.TTS_KEY_PARAM_LANGUAGE; mCachedParams[Engine.TTS_PARAM_POSITION_COUNTRY] = Engine.TTS_KEY_PARAM_COUNTRY; mCachedParams[Engine.TTS_PARAM_POSITION_VARIANT] = Engine.TTS_KEY_PARAM_VARIANT; + mCachedParams[Engine.TTS_PARAM_POSITION_STREAM] = Engine.TTS_KEY_PARAM_STREAM; + mCachedParams[Engine.TTS_PARAM_POSITION_UTTERANCE_ID] = Engine.TTS_KEY_PARAM_UTTERANCE_ID; mCachedParams[Engine.TTS_PARAM_POSITION_RATE + 1] = String.valueOf(Engine.FALLBACK_TTS_DEFAULT_RATE); @@ -177,6 +289,10 @@ public class TextToSpeech { mCachedParams[Engine.TTS_PARAM_POSITION_COUNTRY + 1] = defaultLoc.getISO3Country(); mCachedParams[Engine.TTS_PARAM_POSITION_VARIANT + 1] = defaultLoc.getVariant(); + mCachedParams[Engine.TTS_PARAM_POSITION_STREAM + 1] = + String.valueOf(Engine.TTS_DEFAULT_STREAM); + mCachedParams[Engine.TTS_PARAM_POSITION_UTTERANCE_ID + 1] = ""; + initTts(); } @@ -263,18 +379,24 @@ public class TextToSpeech { return TTS_ERROR; } try { - mITts.addSpeech(text, packagename, resourceId); + mITts.addSpeech(mPackageName, text, packagename, resourceId); return TTS_SUCCESS; } catch (RemoteException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - addSpeech", "RemoteException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (NullPointerException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - addSpeech", "NullPointerException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (IllegalStateException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - addSpeech", "IllegalStateException"); + e.printStackTrace(); mStarted = false; initTts(); } @@ -301,18 +423,127 @@ public class TextToSpeech { return TTS_ERROR; } try { - mITts.addSpeechFile(text, filename); + mITts.addSpeechFile(mPackageName, text, filename); + return TTS_SUCCESS; + } catch (RemoteException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - addSpeech", "RemoteException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (NullPointerException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - addSpeech", "NullPointerException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (IllegalStateException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - addSpeech", "IllegalStateException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } + return TTS_ERROR; + } + } + + + /** + * Adds a mapping between a string of text and a sound resource in a + * package. + * + * @see #TTS.playEarcon(String earcon, int queueMode, String[] params) + * + * @param earcon The name of the earcon + * Example: <b><code>"[tick]"</code></b><br/> + * + * @param packagename + * Pass the packagename of the application that contains the + * resource. If the resource is in your own application (this is + * the most common case), then put the packagename of your + * application here.<br/> + * Example: <b>"com.google.marvin.compass"</b><br/> + * The packagename can be found in the AndroidManifest.xml of + * your application. + * <p> + * <code><manifest xmlns:android="..." + * package="<b>com.google.marvin.compass</b>"></code> + * </p> + * + * @param resourceId + * Example: <b><code>R.raw.tick_snd</code></b> + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. + */ + public int addEarcon(String earcon, String packagename, int resourceId) { + synchronized(mStartLock) { + if (!mStarted) { + return TTS_ERROR; + } + try { + mITts.addEarcon(mPackageName, earcon, packagename, resourceId); + return TTS_SUCCESS; + } catch (RemoteException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - addEarcon", "RemoteException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (NullPointerException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - addEarcon", "NullPointerException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (IllegalStateException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - addEarcon", "IllegalStateException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } + return TTS_ERROR; + } + } + + + /** + * Adds a mapping between a string of text and a sound file. Using this, it + * is possible to add custom earcons. + * + * @param earcon + * The name of the earcon + * @param filename + * The full path to the sound file (for example: + * "/sdcard/mysounds/tick.wav") + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. + */ + public int addEarcon(String earcon, String filename) { + synchronized (mStartLock) { + if (!mStarted) { + return TTS_ERROR; + } + try { + mITts.addEarconFile(mPackageName, earcon, filename); return TTS_SUCCESS; } catch (RemoteException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - addEarcon", "RemoteException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (NullPointerException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - addEarcon", "NullPointerException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (IllegalStateException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - addEarcon", "IllegalStateException"); + e.printStackTrace(); mStarted = false; initTts(); } @@ -347,22 +578,38 @@ public class TextToSpeech { return result; } try { - // TODO support extra parameters, passing cache of current parameters for the moment - result = mITts.speak(text, queueMode, mCachedParams); + if ((params != null) && (!params.isEmpty())) { + String extra = params.get(Engine.TTS_KEY_PARAM_STREAM); + if (extra != null) { + mCachedParams[Engine.TTS_PARAM_POSITION_STREAM + 1] = extra; + } + extra = params.get(Engine.TTS_KEY_PARAM_UTTERANCE_ID); + if (extra != null) { + mCachedParams[Engine.TTS_PARAM_POSITION_UTTERANCE_ID + 1] = extra; + } + } + result = mITts.speak(mPackageName, text, queueMode, mCachedParams); } catch (RemoteException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - speak", "RemoteException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (NullPointerException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - speak", "NullPointerException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (IllegalStateException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - speak", "IllegalStateException"); + e.printStackTrace(); mStarted = false; initTts(); } finally { - return result; + resetCachedParams(); + return result; } } } @@ -388,22 +635,38 @@ public class TextToSpeech { return result; } try { - // TODO support extra parameters, passing null for the moment - result = mITts.playEarcon(earcon, queueMode, null); + if ((params != null) && (!params.isEmpty())) { + String extra = params.get(Engine.TTS_KEY_PARAM_STREAM); + if (extra != null) { + mCachedParams[Engine.TTS_PARAM_POSITION_STREAM + 1] = extra; + } + extra = params.get(Engine.TTS_KEY_PARAM_UTTERANCE_ID); + if (extra != null) { + mCachedParams[Engine.TTS_PARAM_POSITION_UTTERANCE_ID + 1] = extra; + } + } + result = mITts.playEarcon(mPackageName, earcon, queueMode, null); } catch (RemoteException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - playEarcon", "RemoteException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (NullPointerException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - playEarcon", "NullPointerException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (IllegalStateException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - playEarcon", "IllegalStateException"); + e.printStackTrace(); mStarted = false; initTts(); } finally { - return result; + resetCachedParams(); + return result; } } } @@ -419,25 +682,36 @@ public class TextToSpeech { * * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. */ - public int playSilence(long durationInMs, int queueMode) { + public int playSilence(long durationInMs, int queueMode, HashMap<String,String> params) { synchronized (mStartLock) { int result = TTS_ERROR; if (!mStarted) { return result; } try { - // TODO support extra parameters, passing cache of current parameters for the moment - result = mITts.playSilence(durationInMs, queueMode, mCachedParams); + if ((params != null) && (!params.isEmpty())) { + String extra = params.get(Engine.TTS_KEY_PARAM_UTTERANCE_ID); + if (extra != null) { + mCachedParams[Engine.TTS_PARAM_POSITION_UTTERANCE_ID + 1] = extra; + } + } + result = mITts.playSilence(mPackageName, durationInMs, queueMode, mCachedParams); } catch (RemoteException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - playSilence", "RemoteException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (NullPointerException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - playSilence", "NullPointerException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (IllegalStateException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - playSilence", "IllegalStateException"); + e.printStackTrace(); mStarted = false; initTts(); } finally { @@ -461,14 +735,20 @@ public class TextToSpeech { return mITts.isSpeaking(); } catch (RemoteException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - isSpeaking", "RemoteException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (NullPointerException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - isSpeaking", "NullPointerException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (IllegalStateException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - isSpeaking", "IllegalStateException"); + e.printStackTrace(); mStarted = false; initTts(); } @@ -489,17 +769,23 @@ public class TextToSpeech { return result; } try { - result = mITts.stop(); + result = mITts.stop(mPackageName); } catch (RemoteException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - stop", "RemoteException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (NullPointerException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - stop", "NullPointerException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (IllegalStateException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - stop", "IllegalStateException"); + e.printStackTrace(); mStarted = false; initTts(); } finally { @@ -534,10 +820,24 @@ public class TextToSpeech { if (speechRate > 0) { int rate = (int)(speechRate*100); mCachedParams[Engine.TTS_PARAM_POSITION_RATE + 1] = String.valueOf(rate); - result = mITts.setSpeechRate(rate); + // the rate is not set here, instead it is cached so it will be associated + // with all upcoming utterances. + if (speechRate > 0.0f) { + result = TTS_SUCCESS; + } else { + result = TTS_ERROR; + } } - } catch (RemoteException e) { + } catch (NullPointerException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - setSpeechRate", "NullPointerException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (IllegalStateException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - setSpeechRate", "IllegalStateException"); + e.printStackTrace(); mStarted = false; initTts(); } finally { @@ -570,10 +870,24 @@ public class TextToSpeech { } try { if (pitch > 0) { - result = mITts.setPitch((int)(pitch*100)); + result = mITts.setPitch(mPackageName, (int)(pitch*100)); } } catch (RemoteException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - setPitch", "RemoteException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (NullPointerException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - setPitch", "NullPointerException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (IllegalStateException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - setPitch", "IllegalStateException"); + e.printStackTrace(); mStarted = false; initTts(); } finally { @@ -593,7 +907,9 @@ public class TextToSpeech { * @param loc * The locale describing the language to be used. * - * @return Code indicating the support status for the locale. See the TTS_LANG_ codes. + * @return code indicating the support status for the locale. See {@link #TTS_LANG_AVAILABLE}, + * {@link #TTS_LANG_COUNTRY_AVAILABLE}, {@link #TTS_LANG_COUNTRY_VAR_AVAILABLE}, + * {@link #TTS_LANG_MISSING_DATA} and {@link #TTS_LANG_NOT_SUPPORTED}. */ public int setLanguage(Locale loc) { synchronized (mStartLock) { @@ -605,10 +921,27 @@ public class TextToSpeech { mCachedParams[Engine.TTS_PARAM_POSITION_LANGUAGE + 1] = loc.getISO3Language(); mCachedParams[Engine.TTS_PARAM_POSITION_COUNTRY + 1] = loc.getISO3Country(); mCachedParams[Engine.TTS_PARAM_POSITION_VARIANT + 1] = loc.getVariant(); - result = mITts.setLanguage(mCachedParams[Engine.TTS_PARAM_POSITION_LANGUAGE + 1], + + result = mITts.setLanguage(mPackageName, + mCachedParams[Engine.TTS_PARAM_POSITION_LANGUAGE + 1], mCachedParams[Engine.TTS_PARAM_POSITION_COUNTRY + 1], mCachedParams[Engine.TTS_PARAM_POSITION_VARIANT + 1] ); + } catch (RemoteException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - setLanguage", "RemoteException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (NullPointerException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - setLanguage", "NullPointerException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (IllegalStateException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - setLanguage", "IllegalStateException"); + e.printStackTrace(); mStarted = false; initTts(); } finally { @@ -637,6 +970,20 @@ public class TextToSpeech { } } catch (RemoteException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - getLanguage", "RemoteException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (NullPointerException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - getLanguage", "NullPointerException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (IllegalStateException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - getLanguage", "IllegalStateException"); + e.printStackTrace(); mStarted = false; initTts(); } @@ -650,8 +997,9 @@ public class TextToSpeech { * @param loc * The Locale describing the language to be used. * - * @return one of TTS_LANG_NOT_SUPPORTED, TTS_LANG_MISSING_DATA, TTS_LANG_AVAILABLE, - * TTS_LANG_COUNTRY_AVAILABLE, TTS_LANG_COUNTRY_VAR_AVAILABLE. + * @return code indicating the support status for the locale. See {@link #TTS_LANG_AVAILABLE}, + * {@link #TTS_LANG_COUNTRY_AVAILABLE}, {@link #TTS_LANG_COUNTRY_VAR_AVAILABLE}, + * {@link #TTS_LANG_MISSING_DATA} and {@link #TTS_LANG_NOT_SUPPORTED}. */ public int isLanguageAvailable(Locale loc) { synchronized (mStartLock) { @@ -664,6 +1012,20 @@ public class TextToSpeech { loc.getISO3Country(), loc.getVariant()); } catch (RemoteException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - isLanguageAvailable", "RemoteException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (NullPointerException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - isLanguageAvailable", "NullPointerException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (IllegalStateException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - isLanguageAvailable", "IllegalStateException"); + e.printStackTrace(); mStarted = false; initTts(); } finally { @@ -694,26 +1056,98 @@ public class TextToSpeech { return result; } try { - // TODO support extra parameters, passing null for the moment - if (mITts.synthesizeToFile(text, null, filename)){ + if ((params != null) && (!params.isEmpty())) { + // no need to read the stream type here + String extra = params.get(Engine.TTS_KEY_PARAM_UTTERANCE_ID); + if (extra != null) { + mCachedParams[Engine.TTS_PARAM_POSITION_UTTERANCE_ID + 1] = extra; + } + } + if (mITts.synthesizeToFile(mPackageName, text, mCachedParams, filename)){ result = TTS_SUCCESS; } } catch (RemoteException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - synthesizeToFile", "RemoteException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (NullPointerException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - synthesizeToFile", "NullPointerException"); + e.printStackTrace(); mStarted = false; initTts(); } catch (IllegalStateException e) { // TTS died; restart it. + Log.e("TextToSpeech.java - synthesizeToFile", "IllegalStateException"); + e.printStackTrace(); mStarted = false; initTts(); } finally { - return result; + resetCachedParams(); + return result; } } } + + /** + * Convenience method to reset the cached parameters to the current default values + * if they are not persistent between calls to the service. + */ + private void resetCachedParams() { + mCachedParams[Engine.TTS_PARAM_POSITION_STREAM + 1] = + String.valueOf(Engine.TTS_DEFAULT_STREAM); + mCachedParams[Engine.TTS_PARAM_POSITION_UTTERANCE_ID+ 1] = ""; + } + + /** + * Sets the OnUtteranceCompletedListener that will fire when an utterance completes. + * + * @param listener + * The OnUtteranceCompletedListener + * + * @return Code indicating success or failure. See TTS_ERROR and TTS_SUCCESS. + */ + public int setOnUtteranceCompletedListener(
+ final OnUtteranceCompletedListener listener) {
+ synchronized (mStartLock) { + int result = TTS_ERROR; + if (!mStarted) { + return result; + } + mITtscallback = new ITtsCallback.Stub() { + public void utteranceCompleted(String utteranceId) throws RemoteException {
+ if (listener != null) {
+ listener.onUtteranceCompleted(utteranceId);
+ }
+ }
+ }; + try { + result = mITts.registerCallback(mPackageName, mITtscallback); + } catch (RemoteException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - registerCallback", "RemoteException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (NullPointerException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - registerCallback", "NullPointerException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } catch (IllegalStateException e) { + // TTS died; restart it. + Log.e("TextToSpeech.java - registerCallback", "IllegalStateException"); + e.printStackTrace(); + mStarted = false; + initTts(); + } finally { + return result; + } + }
+ } + } diff --git a/core/java/android/text/Html.java b/core/java/android/text/Html.java index 70e1297..380e5fd 100644 --- a/core/java/android/text/Html.java +++ b/core/java/android/text/Html.java @@ -25,6 +25,7 @@ import org.xml.sax.Locator; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; +import android.content.res.ColorStateList; import android.content.res.Resources; import android.graphics.Typeface; import android.graphics.drawable.Drawable; @@ -40,6 +41,7 @@ import android.text.style.StrikethroughSpan; import android.text.style.StyleSpan; import android.text.style.SubscriptSpan; import android.text.style.SuperscriptSpan; +import android.text.style.TextAppearanceSpan; import android.text.style.TypefaceSpan; import android.text.style.URLSpan; import android.text.style.UnderlineSpan; @@ -49,6 +51,7 @@ import com.android.internal.util.XmlUtils; import java.io.IOException; import java.io.StringReader; import java.nio.CharBuffer; +import java.util.HashMap; /** * This class processes HTML strings into displayable styled text. @@ -633,54 +636,25 @@ class HtmlToSpannedConverter implements ContentHandler { if (where != len) { Font f = (Font) obj; - if (f.mColor != null) { - int c = -1; - - if (f.mColor.equalsIgnoreCase("aqua")) { - c = 0x00FFFF; - } else if (f.mColor.equalsIgnoreCase("black")) { - c = 0x000000; - } else if (f.mColor.equalsIgnoreCase("blue")) { - c = 0x0000FF; - } else if (f.mColor.equalsIgnoreCase("fuchsia")) { - c = 0xFF00FF; - } else if (f.mColor.equalsIgnoreCase("green")) { - c = 0x008000; - } else if (f.mColor.equalsIgnoreCase("grey")) { - c = 0x808080; - } else if (f.mColor.equalsIgnoreCase("lime")) { - c = 0x00FF00; - } else if (f.mColor.equalsIgnoreCase("maroon")) { - c = 0x800000; - } else if (f.mColor.equalsIgnoreCase("navy")) { - c = 0x000080; - } else if (f.mColor.equalsIgnoreCase("olive")) { - c = 0x808000; - } else if (f.mColor.equalsIgnoreCase("purple")) { - c = 0x800080; - } else if (f.mColor.equalsIgnoreCase("red")) { - c = 0xFF0000; - } else if (f.mColor.equalsIgnoreCase("silver")) { - c = 0xC0C0C0; - } else if (f.mColor.equalsIgnoreCase("teal")) { - c = 0x008080; - } else if (f.mColor.equalsIgnoreCase("white")) { - c = 0xFFFFFF; - } else if (f.mColor.equalsIgnoreCase("yellow")) { - c = 0xFFFF00; + if (!TextUtils.isEmpty(f.mColor)) { + if (f.mColor.startsWith("@")) { + Resources res = Resources.getSystem(); + String name = f.mColor.substring(1); + int colorRes = res.getIdentifier(name, "color", "android"); + if (colorRes != 0) { + ColorStateList colors = res.getColorStateList(colorRes); + text.setSpan(new TextAppearanceSpan(null, 0, 0, colors, null), + where, len, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } } else { - try { - c = XmlUtils.convertValueToInt(f.mColor, -1); - } catch (NumberFormatException nfe) { - // Can't understand the color, so just drop it. + int c = getHtmlColor(f.mColor); + if (c != -1) { + text.setSpan(new ForegroundColorSpan(c | 0xFF000000), + where, len, + Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); } } - - if (c != -1) { - text.setSpan(new ForegroundColorSpan(c | 0xFF000000), - where, len, - Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); - } } if (f.mFace != null) { @@ -843,4 +817,47 @@ class HtmlToSpannedConverter implements ContentHandler { mLevel = level; } } + + private static HashMap<String,Integer> COLORS = buildColorMap(); + + private static HashMap<String,Integer> buildColorMap() { + HashMap<String,Integer> map = new HashMap<String,Integer>(); + map.put("aqua", 0x00FFFF); + map.put("black", 0x000000); + map.put("blue", 0x0000FF); + map.put("fuchsia", 0xFF00FF); + map.put("green", 0x008000); + map.put("grey", 0x808080); + map.put("lime", 0x00FF00); + map.put("maroon", 0x800000); + map.put("navy", 0x000080); + map.put("olive", 0x808000); + map.put("purple", 0x800080); + map.put("red", 0xFF0000); + map.put("silver", 0xC0C0C0); + map.put("teal", 0x008080); + map.put("white", 0xFFFFFF); + map.put("yellow", 0xFFFF00); + return map; + } + + /** + * Converts an HTML color (named or numeric) to an integer RGB value. + * + * @param color Non-null color string. + * @return A color value, or {@code -1} if the color string could not be interpreted. + */ + private static int getHtmlColor(String color) { + Integer i = COLORS.get(color.toLowerCase()); + if (i != null) { + return i; + } else { + try { + return XmlUtils.convertValueToInt(color, -1); + } catch (NumberFormatException nfe) { + return -1; + } + } + } + } diff --git a/core/java/android/util/DisplayMetrics.java b/core/java/android/util/DisplayMetrics.java index 4179edb..061f98a 100644 --- a/core/java/android/util/DisplayMetrics.java +++ b/core/java/android/util/DisplayMetrics.java @@ -27,17 +27,31 @@ import android.os.*; */ public class DisplayMetrics { /** + * Standard quantized DPI for low-density screens. + */ + public static final int DENSITY_LOW = 120; + + /** + * Standard quantized DPI for medium-density screens. + */ + public static final int DENSITY_MEDIUM = 160; + + /** + * Standard quantized DPI for high-density screens. + */ + public static final int DENSITY_HIGH = 240; + + /** * The reference density used throughout the system. - * - * @hide Pending API council approval */ - public static final int DEFAULT_DENSITY = 160; + public static final int DENSITY_DEFAULT = DENSITY_MEDIUM; /** * The device's density. - * @hide + * @hide becase eventually this should be able to change while + * running, so shouldn't be a constant. */ - public static final int DEVICE_DENSITY = getDeviceDensity(); + public static final int DENSITY_DEVICE = getDeviceDensity(); /** * The absolute width of the display in pixels. @@ -62,7 +76,7 @@ public class DisplayMetrics { * 320x480 but the screen size remained 1.5"x2" then the density would be * increased (probably to 1.5). * - * @see #DEFAULT_DENSITY + * @see #DENSITY_DEFAULT */ public float density; /** @@ -95,10 +109,10 @@ public class DisplayMetrics { public void setToDefaults() { widthPixels = 0; heightPixels = 0; - density = DEVICE_DENSITY / (float) DEFAULT_DENSITY; + density = DENSITY_DEVICE / (float) DENSITY_DEFAULT; scaledDensity = density; - xdpi = DEVICE_DENSITY; - ydpi = DEVICE_DENSITY; + xdpi = DENSITY_DEVICE; + ydpi = DENSITY_DEVICE; } /** @@ -109,60 +123,77 @@ public class DisplayMetrics { */ public void updateMetrics(CompatibilityInfo compatibilityInfo, int orientation, int screenLayout) { - int xOffset = 0; - if (!compatibilityInfo.isConfiguredExpandable()) { - // Note: this assume that configuration is updated before calling - // updateMetrics method. - if (screenLayout == Configuration.SCREENLAYOUT_LARGE) { - // This is a large screen device and the app is not - // compatible with large screens, to diddle it. - + boolean expandable = compatibilityInfo.isConfiguredExpandable(); + boolean largeScreens = compatibilityInfo.isConfiguredLargeScreens(); + + // Note: this assume that configuration is updated before calling + // updateMetrics method. + if (!expandable) { + if ((screenLayout&Configuration.SCREENLAYOUT_COMPAT_NEEDED) == 0) { + expandable = true; + // the current screen size is compatible with non-resizing apps. + compatibilityInfo.setExpandable(true); + } else { compatibilityInfo.setExpandable(false); - // Figure out the compatibility width and height of the screen. - int defaultWidth; - int defaultHeight; - switch (orientation) { - case Configuration.ORIENTATION_LANDSCAPE: { - defaultWidth = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_HEIGHT * density); - defaultHeight = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_WIDTH * density); - break; - } - case Configuration.ORIENTATION_PORTRAIT: - case Configuration.ORIENTATION_SQUARE: - default: { - defaultWidth = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_WIDTH * density); - defaultHeight = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_HEIGHT * density); - break; - } - case Configuration.ORIENTATION_UNDEFINED: { - // don't change - return; - } + } + } + if (!largeScreens) { + if ((screenLayout&Configuration.SCREENLAYOUT_SIZE_MASK) + != Configuration.SCREENLAYOUT_SIZE_LARGE) { + largeScreens = true; + // the current screen size is not large. + compatibilityInfo.setLargeScreens(true); + } else { + compatibilityInfo.setLargeScreens(false); + } + } + + if (!expandable || !largeScreens) { + // This is a larger screen device and the app is not + // compatible with large screens, so diddle it. + + // Figure out the compatibility width and height of the screen. + int defaultWidth; + int defaultHeight; + switch (orientation) { + case Configuration.ORIENTATION_LANDSCAPE: { + defaultWidth = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_HEIGHT * density + + 0.5f); + defaultHeight = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_WIDTH * density + + 0.5f); + break; } - - if (defaultWidth < widthPixels) { - // content/window's x offset in original pixels - xOffset = ((widthPixels - defaultWidth) / 2); - widthPixels = defaultWidth; + case Configuration.ORIENTATION_PORTRAIT: + case Configuration.ORIENTATION_SQUARE: + default: { + defaultWidth = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_WIDTH * density + + 0.5f); + defaultHeight = (int)(CompatibilityInfo.DEFAULT_PORTRAIT_HEIGHT * density + + 0.5f); + break; } - if (defaultHeight < heightPixels) { - heightPixels = defaultHeight; + case Configuration.ORIENTATION_UNDEFINED: { + // don't change + return; } - - } else { - // the screen size is same as expected size. make it expandable - compatibilityInfo.setExpandable(true); + } + + if (defaultWidth < widthPixels) { + // content/window's x offset in original pixels + widthPixels = defaultWidth; + } + if (defaultHeight < heightPixels) { + heightPixels = defaultHeight; } } - compatibilityInfo.setVisibleRect(xOffset, widthPixels, heightPixels); if (compatibilityInfo.isScalingRequired()) { float invertedRatio = compatibilityInfo.applicationInvertedScale; density *= invertedRatio; scaledDensity *= invertedRatio; xdpi *= invertedRatio; ydpi *= invertedRatio; - widthPixels *= invertedRatio; - heightPixels *= invertedRatio; + widthPixels = (int) (widthPixels * invertedRatio + 0.5f); + heightPixels = (int) (heightPixels * invertedRatio + 0.5f); } } @@ -179,6 +210,6 @@ public class DisplayMetrics { // The reason for this is that ro.sf.lcd_density is write-once and is // set by the init process when it parses build.prop before anything else. return SystemProperties.getInt("qemu.sf.lcd_density", - SystemProperties.getInt("ro.sf.lcd_density", DEFAULT_DENSITY)); + SystemProperties.getInt("ro.sf.lcd_density", DENSITY_DEFAULT)); } } diff --git a/core/java/android/util/TypedValue.java b/core/java/android/util/TypedValue.java index d4ba9e2..ed45298 100644 --- a/core/java/android/util/TypedValue.java +++ b/core/java/android/util/TypedValue.java @@ -140,12 +140,16 @@ public class TypedValue { /** * If {@link #density} is equal to this value, then the density should be - * treated as the system's default density value: {@link DisplayMetrics#DEFAULT_DENSITY}. - * - * @hide Pending API council approval + * treated as the system's default density value: {@link DisplayMetrics#DENSITY_DEFAULT}. */ public static final int DENSITY_DEFAULT = 0; + /** + * If {@link #density} is equal to this value, then there is no density + * associated with the resource and it should not be scaled. + */ + public static final int DENSITY_NONE = 0xffff; + /* ------------------------------------------------------------ */ /** The type held by this value, as defined by the constants here. @@ -171,8 +175,6 @@ public class TypedValue { /** * If the Value came from a resource, this holds the corresponding pixel density. - * - * @hide Pending API council approval * */ public int density; diff --git a/core/java/android/view/Display.java b/core/java/android/view/Display.java index 09ebeed..5551f64 100644 --- a/core/java/android/view/Display.java +++ b/core/java/android/view/Display.java @@ -117,5 +117,32 @@ public class Display private static final Object mStaticInit = new Object(); private static boolean mInitialized = false; + + /** + * Returns a display object which uses the metric's width/height instead. + * @hide + */ + public static Display createMetricsBasedDisplay(int displayId, DisplayMetrics metrics) { + return new CompatibleDisplay(displayId, metrics); + } + + private static class CompatibleDisplay extends Display { + private final DisplayMetrics mMetrics; + + private CompatibleDisplay(int displayId, DisplayMetrics metrics) { + super(displayId); + mMetrics = metrics; + } + + @Override + public int getWidth() { + return mMetrics.widthPixels; + } + + @Override + public int getHeight() { + return mMetrics.heightPixels; + } + } } diff --git a/core/java/android/view/Surface.java b/core/java/android/view/Surface.java index 0178d63..83c30e1 100644 --- a/core/java/android/view/Surface.java +++ b/core/java/android/view/Surface.java @@ -19,6 +19,7 @@ package android.view; import android.graphics.*; import android.os.Parcelable; import android.os.Parcel; +import android.util.DisplayMetrics; import android.util.Log; /** @@ -131,6 +132,10 @@ public class Surface implements Parcelable { @SuppressWarnings("unused") private Canvas mCanvas; + // The display metrics used to provide the pseudo canvas size for applications + // running in compatibility mode. This is set to null for regular mode. + private DisplayMetrics mDisplayMetrics; + /** * Exception thrown when a surface couldn't be created or resized */ @@ -167,7 +172,23 @@ public class Surface implements Parcelable { * {@hide} */ public Surface() { - mCanvas = new Canvas(); + mCanvas = new Canvas() { + @Override + public int getWidth() { + return mDisplayMetrics == null ? super.getWidth() : mDisplayMetrics.widthPixels; + } + @Override + public int getHeight() { + return mDisplayMetrics == null ? super.getHeight() : mDisplayMetrics.heightPixels; + } + }; + } + + /** + * Sets the display metrics used to provide canva's width/height in comaptibility mode. + */ + void setCompatibleDisplayMetrics(DisplayMetrics metrics) { + mDisplayMetrics = metrics; } /** diff --git a/core/java/android/view/SurfaceView.java b/core/java/android/view/SurfaceView.java index 45b0f0a..95bba97 100644 --- a/core/java/android/view/SurfaceView.java +++ b/core/java/android/view/SurfaceView.java @@ -17,7 +17,7 @@ package android.view; import android.content.Context; -import android.content.res.CompatibilityInfo; +import android.content.res.Resources; import android.content.res.CompatibilityInfo.Translator; import android.graphics.Canvas; import android.graphics.PixelFormat; @@ -256,9 +256,9 @@ public class SurfaceView extends View { public boolean dispatchTouchEvent(MotionEvent event) { // SurfaceView uses pre-scaled size unless fixed size is requested. This hook // scales the event back to the pre-scaled coordinates for such surface. - if (mRequestedWidth < 0 && mTranslator != null) { + if (mScaled) { MotionEvent scaledBack = MotionEvent.obtain(event); - scaledBack.scale(mTranslator.applicationScale); + mTranslator.translateEventInScreenToAppWindow(event); try { return super.dispatchTouchEvent(scaledBack); } finally { @@ -290,15 +290,23 @@ public class SurfaceView extends View { public void setWindowType(int type) { mWindowType = type; } + + boolean mScaled = false; private void updateWindow(boolean force) { if (!mHaveFrame) { return; } - mTranslator = ((ViewRoot)getRootView().getParent()).mTranslator; + ViewRoot viewRoot = (ViewRoot) getRootView().getParent(); + mTranslator = viewRoot.mTranslator; float appScale = mTranslator == null ? 1.0f : mTranslator.applicationScale; + Resources res = getContext().getResources(); + if (mTranslator != null || !res.getCompatibilityInfo().supportsScreen()) { + mSurface.setCompatibleDisplayMetrics(res.getDisplayMetrics()); + } + int myWidth = mRequestedWidth; if (myWidth <= 0) myWidth = getWidth(); int myHeight = mRequestedHeight; @@ -306,9 +314,12 @@ public class SurfaceView extends View { // Use original size if the app specified the size of the view, // and let the flinger to scale up. - if (mRequestedWidth <= 0 && mTranslator != null && mTranslator.scalingRequired) { - myWidth *= appScale; - myHeight *= appScale; + if (mRequestedWidth <= 0 && mTranslator != null) { + myWidth = (int) (myWidth * appScale + 0.5f); + myHeight = (int) (myHeight * appScale + 0.5f); + mScaled = true; + } else { + mScaled = false; } getLocationInWindow(mLocation); @@ -352,8 +363,10 @@ public class SurfaceView extends View { | WindowManager.LayoutParams.FLAG_SCALED | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE - | WindowManager.LayoutParams.FLAG_NO_COMPATIBILITY_SCALING ; + if (!getContext().getResources().getCompatibilityInfo().supportsScreen()) { + mLayout.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; + } mLayout.memoryType = mRequestedType; @@ -533,6 +546,7 @@ public class SurfaceView extends View { private SurfaceHolder mSurfaceHolder = new SurfaceHolder() { private static final String LOG_TAG = "SurfaceHolder"; + private int mSaveCount; public boolean isCreating() { return mIsCreating; @@ -627,6 +641,10 @@ public class SurfaceView extends View { if (localLOGV) Log.i(TAG, "Returned canvas: " + c); if (c != null) { mLastLockTime = SystemClock.uptimeMillis(); + if (mScaled) { + mSaveCount = c.save(); + mTranslator.translateCanvas(c); + } return c; } @@ -649,6 +667,9 @@ public class SurfaceView extends View { } public void unlockCanvasAndPost(Canvas canvas) { + if (mScaled) { + canvas.restoreToCount(mSaveCount); + } mSurface.unlockCanvasAndPost(canvas); mSurfaceLock.unlock(); } diff --git a/core/java/android/view/ViewRoot.java b/core/java/android/view/ViewRoot.java index 6f6e224..2f92b32 100644 --- a/core/java/android/view/ViewRoot.java +++ b/core/java/android/view/ViewRoot.java @@ -42,6 +42,7 @@ import android.view.inputmethod.InputMethodManager; import android.widget.Scroller; import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; +import android.content.res.Resources; import android.content.Context; import android.app.ActivityManagerNative; import android.Manifest; @@ -385,10 +386,15 @@ public final class ViewRoot extends Handler implements ViewParent, if (mView == null) { mView = view; mWindowAttributes.copyFrom(attrs); - - CompatibilityInfo compatibilityInfo = - mView.getContext().getResources().getCompatibilityInfo(); + attrs = mWindowAttributes; + Resources resources = mView.getContext().getResources(); + CompatibilityInfo compatibilityInfo = resources.getCompatibilityInfo(); mTranslator = compatibilityInfo.getTranslator(attrs); + + if (mTranslator != null || !compatibilityInfo.supportsScreen()) { + mSurface.setCompatibleDisplayMetrics(resources.getDisplayMetrics()); + } + boolean restore = false; if (attrs != null && mTranslator != null) { restore = true; @@ -397,11 +403,14 @@ public final class ViewRoot extends Handler implements ViewParent, } if (DEBUG_LAYOUT) Log.d(TAG, "WindowLayout in setView:" + attrs); + if (!compatibilityInfo.supportsScreen()) { + attrs.flags |= WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; + } + mSoftInputMode = attrs.softInputMode; mWindowAttributesChanged = true; mAttachInfo.mRootView = view; - mAttachInfo.mScalingRequired = - mTranslator == null ? false : mTranslator.scalingRequired; + mAttachInfo.mScalingRequired = mTranslator == null ? false : true; mAttachInfo.mApplicationScale = mTranslator == null ? 1.0f : mTranslator.applicationScale; if (panelParentView != null) { @@ -493,8 +502,12 @@ public final class ViewRoot extends Handler implements ViewParent, void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) { synchronized (this) { int oldSoftInputMode = mWindowAttributes.softInputMode; + // preserve compatible window flag if exists. + int compatibleWindowFlag = + mWindowAttributes.flags & WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; mWindowAttributes.copyFrom(attrs); - + mWindowAttributes.flags |= compatibleWindowFlag; + if (newView) { mSoftInputMode = attrs.softInputMode; requestLayout(); @@ -899,7 +912,8 @@ public final class ViewRoot extends Handler implements ViewParent, mHeight = frame.height(); if (initialized) { - mGlCanvas.setViewport((int) (mWidth * appScale), (int) (mHeight * appScale)); + mGlCanvas.setViewport((int) (mWidth * appScale + 0.5f), + (int) (mHeight * appScale + 0.5f)); } boolean focusChangedDueToTouchMode = ensureTouchModeLocally( @@ -1229,7 +1243,7 @@ public final class ViewRoot extends Handler implements ViewParent, if (fullRedrawNeeded) { mAttachInfo.mIgnoreDirtyState = true; - dirty.union(0, 0, (int) (mWidth * appScale), (int) (mHeight * appScale)); + dirty.union(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f)); } if (DEBUG_ORIENTATION || DEBUG_DRAW) { @@ -1297,7 +1311,8 @@ public final class ViewRoot extends Handler implements ViewParent, if (DEBUG_DRAW) { Context cxt = mView.getContext(); Log.i(TAG, "Drawing: package:" + cxt.getPackageName() + - ", metrics=" + mView.getContext().getResources().getDisplayMetrics()); + ", metrics=" + cxt.getResources().getDisplayMetrics() + + ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo()); } int saveCount = canvas.save(Canvas.MATRIX_SAVE_FLAG); try { @@ -1363,6 +1378,15 @@ public final class ViewRoot extends Handler implements ViewParent, // is non-null and we just want to scroll to whatever that // rectangle is). View focus = mRealFocusedView; + + // When in touch mode, focus points to the previously focused view, + // which may have been removed from the view hierarchy. The following + // line checks whether the view is still in the hierarchy + if (focus == null || focus.getParent() == null) { + mRealFocusedView = null; + return false; + } + if (focus != mLastScrolledFocus) { // If the focus has changed, then ignore any requests to scroll // to a rectangle; first we want to make sure the entire focus @@ -1731,7 +1755,8 @@ public final class ViewRoot extends Handler implements ViewParent, if (mGlCanvas != null) { float appScale = mAttachInfo.mApplicationScale; mGlCanvas.setViewport( - (int) (mWidth * appScale), (int) (mHeight * appScale)); + (int) (mWidth * appScale + 0.5f), + (int) (mHeight * appScale + 0.5f)); } } } @@ -2376,8 +2401,8 @@ public final class ViewRoot extends Handler implements ViewParent, } int relayoutResult = sWindowSession.relayout( mWindow, params, - (int) (mView.mMeasuredWidth * appScale), - (int) (mView.mMeasuredHeight * appScale), + (int) (mView.mMeasuredWidth * appScale + 0.5f), + (int) (mView.mMeasuredHeight * appScale + 0.5f), viewVisibility, insetsPending, mWinFrame, mPendingContentInsets, mPendingVisibleInsets, mSurface); if (restore) { diff --git a/core/java/android/view/Window.java b/core/java/android/view/Window.java index d7457a0..576c8c1 100644 --- a/core/java/android/view/Window.java +++ b/core/java/android/view/Window.java @@ -358,6 +358,8 @@ public abstract class Window { private class LocalWindowManager implements WindowManager { LocalWindowManager(WindowManager wm) { mWindowManager = wm; + mDefaultDisplay = mContext.getResources().getDefaultDisplay( + mWindowManager.getDefaultDisplay()); } public final void addView(View view, ViewGroup.LayoutParams params) { @@ -420,10 +422,12 @@ public abstract class Window { } public Display getDefaultDisplay() { - return mWindowManager.getDefaultDisplay(); + return mDefaultDisplay; } - WindowManager mWindowManager; + private final WindowManager mWindowManager; + + private final Display mDefaultDisplay; } /** diff --git a/core/java/android/view/WindowManager.java b/core/java/android/view/WindowManager.java index bdb86d7..c0be9e8 100644 --- a/core/java/android/view/WindowManager.java +++ b/core/java/android/view/WindowManager.java @@ -483,11 +483,12 @@ public interface WindowManager extends ViewManager { * {@hide} */ public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000; - /** Window flag: special flag to let a window ignore the compatibility scaling. - * This is used by SurfaceView to create a window that does not scale the content. + /** Window flag: special flag to limit the size of the window to be + * original size ([320x480] x density). Used to create window for applications + * running under compatibility mode. * * {@hide} */ - public static final int FLAG_NO_COMPATIBILITY_SCALING = 0x00100000; + public static final int FLAG_COMPATIBLE_WINDOW = 0x00100000; /** Window flag: a special option intended for system dialogs. When * this flag is set, the window will demand focus unconditionally when @@ -978,6 +979,9 @@ public interface WindowManager extends ViewManager { sb.append(" or="); sb.append(screenOrientation); } + if ((flags & FLAG_COMPATIBLE_WINDOW) != 0) { + sb.append(" compatible=true"); + } sb.append('}'); return sb.toString(); } @@ -987,13 +991,13 @@ public interface WindowManager extends ViewManager { * @hide */ public void scale(float scale) { - x *= scale; - y *= scale; + x = (int) (x * scale + 0.5f); + y = (int) (y * scale + 0.5f); if (width > 0) { - width *= scale; + width = (int) (width * scale + 0.5f); } if (height > 0) { - height *= scale; + height = (int) (height * scale + 0.5f); } } diff --git a/core/java/android/webkit/GearsPermissionsManager.java b/core/java/android/webkit/GearsPermissionsManager.java new file mode 100644 index 0000000..e70e449 --- /dev/null +++ b/core/java/android/webkit/GearsPermissionsManager.java @@ -0,0 +1,240 @@ +/* + * Copyright (C) 2009 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.content.ContentResolver; +import android.content.ContentValues; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.database.ContentObserver; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteException; +import android.database.sqlite.SQLiteStatement; +import android.os.Handler; +import android.preference.PreferenceManager; +import android.provider.Settings; +import android.util.Log; + +import java.io.File; +import java.util.HashSet; + +/** + * Donut-specific hack to keep Gears permissions in sync with the + * system location setting. + */ +class GearsPermissionsManager { + // The application context. + Context mContext; + // The path to gears.so. + private String mGearsPath; + + // The Gears permissions database directory. + private final static String GEARS_DATABASE_DIR = "gears"; + // The Gears permissions database file name. + private final static String GEARS_DATABASE_FILE = "permissions.db"; + // The Gears location permissions table. + private final static String GEARS_LOCATION_ACCESS_TABLE_NAME = + "LocationAccess"; + // The Gears storage access permissions table. + private final static String GEARS_STORAGE_ACCESS_TABLE_NAME = "Access"; + // The Gears permissions db schema version table. + private final static String GEARS_SCHEMA_VERSION_TABLE_NAME = + "VersionInfo"; + // The Gears permission value that denotes "allow access to location". + private static final int GEARS_ALLOW_LOCATION_ACCESS = 1; + // The shared pref name. + private static final String LAST_KNOWN_LOCATION_SETTING = + "lastKnownLocationSystemSetting"; + // The Browser package name. + private static final String BROWSER_PACKAGE_NAME = "com.android.browser"; + // The Secure Settings observer that will be notified when the system + // location setting changes. + private SecureSettingsObserver mSettingsObserver; + // The Google URLs whitelisted for Gears location access. + private static HashSet<String> sGearsWhiteList; + + static { + sGearsWhiteList = new HashSet<String>(); + // NOTE: DO NOT ADD A "/" AT THE END! + sGearsWhiteList.add("http://www.google.com"); + sGearsWhiteList.add("http://www.google.co.uk"); + } + + private static final String LOGTAG = "webcore"; + static final boolean DEBUG = false; + static final boolean LOGV_ENABLED = DEBUG; + + GearsPermissionsManager(Context context, String gearsPath) { + mContext = context; + mGearsPath = gearsPath; + } + + public void doCheckAndStartObserver() { + // Are we running in the browser? + if (!BROWSER_PACKAGE_NAME.equals(mContext.getPackageName())) { + return; + } + // Do the check. + checkGearsPermissions(); + // Install the observer. + mSettingsObserver = new SecureSettingsObserver(); + mSettingsObserver.observe(); + } + + private void checkGearsPermissions() { + // Get the current system settings. + int setting = Settings.Secure.getInt(mContext.getContentResolver(), + Settings.Secure.USE_LOCATION_FOR_SERVICES, -1); + // Check if we need to set the Gears permissions. + if (setting != -1 && locationSystemSettingChanged(setting)) { + setGearsPermissionForGoogleDomains(setting); + } + } + + private boolean locationSystemSettingChanged(int newSetting) { + SharedPreferences prefs = + PreferenceManager.getDefaultSharedPreferences(mContext); + int oldSetting = 0; + oldSetting = prefs.getInt(LAST_KNOWN_LOCATION_SETTING, oldSetting); + if (oldSetting == newSetting) { + return false; + } + Editor ed = prefs.edit(); + ed.putInt(LAST_KNOWN_LOCATION_SETTING, newSetting); + ed.commit(); + return true; + } + + private void setGearsPermissionForGoogleDomains(int systemPermission) { + // Transform the system permission into a boolean flag. When this + // flag is true, it means the origins in gGearsWhiteList are added + // to the Gears location permission table with permission 1 (allowed). + // When the flag is false, the origins in gGearsWhiteList are removed + // from the Gears location permission table. Next time the user + // navigates to one of these origins, she will see the normal Gears + // permission prompt. + boolean addToGearsLocationTable = (systemPermission == 1 ? true : false); + // Build the path to the Gears library. + + File file = new File(mGearsPath).getParentFile(); + if (file == null) { + return; + } + // Build the Gears database file name. + file = new File(file.getAbsolutePath() + File.separator + + GEARS_DATABASE_DIR + File.separator + GEARS_DATABASE_FILE); + // Remember whether or not we need to create the LocationAccess table. + boolean needToCreateTables = !file.exists(); + // If the database file does not yet exist and the system location + // setting says that the Gears origins need to be removed from the + // location permission table, it means that we don't actually need + // to do anything at all. + if (needToCreateTables && !addToGearsLocationTable) { + return; + } + // Try opening the Gears database. + SQLiteDatabase permissions; + try { + permissions = SQLiteDatabase.openOrCreateDatabase(file, null); + } catch (SQLiteException e) { + if (LOGV_ENABLED) { + Log.v(LOGTAG, "Could not open Gears permission DB: " + + e.getMessage()); + } + // Just bail out. + return; + } + // We now have a database open. Begin a transaction. + permissions.beginTransaction(); + try { + if (needToCreateTables) { + // Create the tables. Note that this creates the + // Gears tables for the permissions DB schema version 2. + // The Gears schema upgrade process will take care of the rest. + // First, the storage access table. + SQLiteStatement statement = permissions.compileStatement( + "CREATE TABLE IF NOT EXISTS " + + GEARS_STORAGE_ACCESS_TABLE_NAME + + " (Name TEXT UNIQUE, Value)"); + statement.execute(); + // Next the location access table. + statement = permissions.compileStatement( + "CREATE TABLE IF NOT EXISTS " + + GEARS_LOCATION_ACCESS_TABLE_NAME + + " (Name TEXT UNIQUE, Value)"); + statement.execute(); + // Finally, the schema version table. + statement = permissions.compileStatement( + "CREATE TABLE IF NOT EXISTS " + + GEARS_SCHEMA_VERSION_TABLE_NAME + + " (Name TEXT UNIQUE, Value)"); + statement.execute(); + // Set the schema version to 2. + ContentValues schema = new ContentValues(); + schema.put("Name", "Version"); + schema.put("Value", 2); + permissions.insert(GEARS_SCHEMA_VERSION_TABLE_NAME, null, + schema); + } + + if (addToGearsLocationTable) { + ContentValues permissionValues = new ContentValues(); + + for (String url : sGearsWhiteList) { + permissionValues.put("Name", url); + permissionValues.put("Value", GEARS_ALLOW_LOCATION_ACCESS); + permissions.replace(GEARS_LOCATION_ACCESS_TABLE_NAME, null, + permissionValues); + permissionValues.clear(); + } + } else { + for (String url : sGearsWhiteList) { + permissions.delete(GEARS_LOCATION_ACCESS_TABLE_NAME, "Name=?", + new String[] { url }); + } + } + // Commit the transaction. + permissions.setTransactionSuccessful(); + } catch (SQLiteException e) { + if (LOGV_ENABLED) { + Log.v(LOGTAG, "Could not set the Gears permissions: " + + e.getMessage()); + } + } finally { + permissions.endTransaction(); + permissions.close(); + } + } + + class SecureSettingsObserver extends ContentObserver { + SecureSettingsObserver() { + super(new Handler()); + } + + void observe() { + ContentResolver resolver = mContext.getContentResolver(); + resolver.registerContentObserver(Settings.Secure.getUriFor( + Settings.Secure.USE_LOCATION_FOR_SERVICES), false, this); + } + + @Override + public void onChange(boolean selfChange) { + checkGearsPermissions(); + } + } +} diff --git a/core/java/android/webkit/LoadListener.java b/core/java/android/webkit/LoadListener.java index 39360cd..474fa82 100644 --- a/core/java/android/webkit/LoadListener.java +++ b/core/java/android/webkit/LoadListener.java @@ -38,6 +38,7 @@ import com.android.internal.R; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; +import java.util.HashSet; import java.util.Map; import java.util.Vector; import java.util.regex.Pattern; @@ -72,7 +73,12 @@ class LoadListener extends Handler implements EventHandler { private static final int HTTP_NOT_FOUND = 404; private static final int HTTP_PROXY_AUTH = 407; - private static final String CERT_MIMETYPE = "application/x-x509-ca-cert"; + private static HashSet<String> sCertificateMimeTypeMap; + static { + sCertificateMimeTypeMap = new HashSet<String>(); + sCertificateMimeTypeMap.add("application/x-x509-ca-cert"); + sCertificateMimeTypeMap.add("application/x-pkcs12"); + } private static int sNativeLoaderCount; @@ -318,7 +324,17 @@ class LoadListener extends Handler implements EventHandler { if (mMimeType.equalsIgnoreCase("text/plain") || mMimeType.equalsIgnoreCase("application/octet-stream")) { - String newMimeType = guessMimeTypeFromExtension(); + // for attachment, use the filename in the Content-Disposition + // to guess the mimetype + String contentDisposition = headers.getContentDisposition(); + String url = null; + if (contentDisposition != null) { + url = URLUtil.parseContentDisposition(contentDisposition); + } + if (url == null) { + url = mUrl; + } + String newMimeType = guessMimeTypeFromExtension(url); if (newMimeType != null) { mMimeType = newMimeType; } @@ -936,7 +952,7 @@ class LoadListener extends Handler implements EventHandler { // This commits the headers without checking the response status code. private void commitHeaders() { - if (mIsMainPageLoader && CERT_MIMETYPE.equals(mMimeType)) { + if (mIsMainPageLoader && sCertificateMimeTypeMap.contains(mMimeType)) { // In the case of downloading certificate, we will save it to the // Keystore in commitLoad. Do not call webcore. return; @@ -982,7 +998,7 @@ class LoadListener extends Handler implements EventHandler { private void commitLoad() { if (mCancelled) return; - if (mIsMainPageLoader && CERT_MIMETYPE.equals(mMimeType)) { + if (mIsMainPageLoader && sCertificateMimeTypeMap.contains(mMimeType)) { // In the case of downloading certificate, we will save it to the // Keystore and stop the current loading so that it will not // generate a new history page @@ -1409,7 +1425,7 @@ class LoadListener extends Handler implements EventHandler { // of frames. If no content-type was specified, it is fine to // default to text/html. mMimeType = "text/html"; - String newMimeType = guessMimeTypeFromExtension(); + String newMimeType = guessMimeTypeFromExtension(mUrl); if (newMimeType != null) { mMimeType = newMimeType; } @@ -1419,15 +1435,15 @@ class LoadListener extends Handler implements EventHandler { /** * guess MIME type based on the file extension. */ - private String guessMimeTypeFromExtension() { + private String guessMimeTypeFromExtension(String url) { // PENDING: need to normalize url if (WebView.LOGV_ENABLED) { - Log.v(LOGTAG, "guessMimeTypeFromExtension: mURL = " + mUrl); + Log.v(LOGTAG, "guessMimeTypeFromExtension: url = " + url); } String mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension( - MimeTypeMap.getFileExtensionFromUrl(mUrl)); + MimeTypeMap.getFileExtensionFromUrl(url)); if (mimeType != null) { // XXX: Until the servers send us either correct xhtml or diff --git a/core/java/android/webkit/MimeTypeMap.java b/core/java/android/webkit/MimeTypeMap.java index 85c2275..fdbc692 100644 --- a/core/java/android/webkit/MimeTypeMap.java +++ b/core/java/android/webkit/MimeTypeMap.java @@ -335,6 +335,7 @@ public /* package */ class MimeTypeMap { sMimeTypeMap.loadEntry("application/x-object", "o", false); sMimeTypeMap.loadEntry("application/x-oz-application", "oza", false); + sMimeTypeMap.loadEntry("application/x-pkcs12", "p12", false); sMimeTypeMap.loadEntry("application/x-pkcs7-certreqresp", "p7r", false); sMimeTypeMap.loadEntry("application/x-pkcs7-crl", "crl", false); diff --git a/core/java/android/webkit/URLUtil.java b/core/java/android/webkit/URLUtil.java index d6ac3e9..9889fe9 100644 --- a/core/java/android/webkit/URLUtil.java +++ b/core/java/android/webkit/URLUtil.java @@ -348,7 +348,7 @@ public final class URLUtil { * This header provides a filename for content that is going to be * downloaded to the file system. We only support the attachment type. */ - private static String parseContentDisposition(String contentDisposition) { + static String parseContentDisposition(String contentDisposition) { try { Matcher m = CONTENT_DISPOSITION_PATTERN.matcher(contentDisposition); if (m.find()) { diff --git a/core/java/android/webkit/WebSettings.java b/core/java/android/webkit/WebSettings.java index ec671d5..c5012f1 100644 --- a/core/java/android/webkit/WebSettings.java +++ b/core/java/android/webkit/WebSettings.java @@ -17,13 +17,13 @@ package android.webkit; import android.content.Context; +import android.content.pm.PackageManager; import android.os.Build; import android.os.Handler; import android.os.Message; import android.provider.Checkin; import java.lang.SecurityException; -import android.content.pm.PackageManager; import java.util.Locale; @@ -176,6 +176,9 @@ public class WebSettings { private boolean mBuiltInZoomControls = false; private boolean mAllowFileAccess = true; + // The Gears permissions manager. Only in Donut. + static GearsPermissionsManager sGearsPermissionsManager; + // Class to handle messages before WebCore is ready. private class EventHandler { // Message id for syncing @@ -1148,6 +1151,7 @@ public class WebSettings { if (WebView.DEBUG) { junit.framework.Assert.assertTrue(frame.mNativeFrame != 0); } + checkGearsPermissions(); nativeSync(frame.mNativeFrame); mSyncPending = false; mEventHandler.createHandler(); @@ -1163,6 +1167,23 @@ public class WebSettings { return size; } + private void checkGearsPermissions() { + // Did we already check the permissions at startup? + if (sGearsPermissionsManager != null) { + return; + } + // Is the pluginsPath sane? + String pluginsPath = getPluginsPath(); + if (pluginsPath == null || pluginsPath.length() == 0) { + // We don't yet have a meaningful plugin path, so + // we can't do anything about the Gears permissions. + return; + } + sGearsPermissionsManager = + new GearsPermissionsManager(mContext, pluginsPath); + sGearsPermissionsManager.doCheckAndStartObserver(); + } + /* Post a SYNC message to handle syncing the native settings. */ private synchronized void postSync() { // Only post if a sync is not pending diff --git a/core/java/android/widget/AbsListView.java b/core/java/android/widget/AbsListView.java index f9ca8cb..777beed 100644 --- a/core/java/android/widget/AbsListView.java +++ b/core/java/android/widget/AbsListView.java @@ -720,7 +720,7 @@ public abstract class AbsListView extends AdapterView<ListAdapter> implements Te @Override public void getFocusedRect(Rect r) { View view = getSelectedView(); - if (view != null) { + if (view != null && view.getParent() == this) { // the focused rectangle of the selected view offset into the // coordinate space of this view. view.getFocusedRect(r); diff --git a/core/java/android/widget/AppSecurityPermissions.java b/core/java/android/widget/AppSecurityPermissions.java index c4b5ef8..6579660 100755 --- a/core/java/android/widget/AppSecurityPermissions.java +++ b/core/java/android/widget/AppSecurityPermissions.java @@ -18,6 +18,7 @@ package android.widget; import com.android.internal.R; import android.content.Context; +import android.content.res.Resources; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageParser; @@ -319,15 +320,14 @@ public class AppSecurityPermissions implements View.OnClickListener { boolean dangerous) { View permView = mInflater.inflate(R.layout.app_permission_item, null); Drawable icon = dangerous ? mDangerousIcon : mNormalIcon; - int grpColor = dangerous ? R.color.perms_dangerous_grp_color : - R.color.perms_normal_grp_color; - int permColor = dangerous ? R.color.perms_dangerous_perm_color : - R.color.perms_normal_perm_color; TextView permGrpView = (TextView) permView.findViewById(R.id.permission_group); TextView permDescView = (TextView) permView.findViewById(R.id.permission_list); - permGrpView.setTextColor(mContext.getResources().getColor(grpColor)); - permDescView.setTextColor(mContext.getResources().getColor(permColor)); + if (dangerous) { + final Resources resources = mContext.getResources(); + permGrpView.setTextColor(resources.getColor(R.color.perms_dangerous_grp_color)); + permDescView.setTextColor(resources.getColor(R.color.perms_dangerous_perm_color)); + } ImageView imgView = (ImageView)permView.findViewById(R.id.perm_icon); imgView.setImageDrawable(icon); diff --git a/core/java/android/widget/AutoCompleteTextView.java b/core/java/android/widget/AutoCompleteTextView.java index 675aba2..4bc00de 100644 --- a/core/java/android/widget/AutoCompleteTextView.java +++ b/core/java/android/widget/AutoCompleteTextView.java @@ -31,6 +31,7 @@ import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.view.inputmethod.CompletionInfo; import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.EditorInfo; @@ -141,6 +142,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe mPopup = new PopupWindow(context, attrs, com.android.internal.R.attr.autoCompleteTextViewStyle); + mPopup.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE); TypedArray a = context.obtainStyledAttributes( @@ -208,8 +210,7 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe if (mDropDownAlwaysVisible && mPopup.isShowing() && mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED) { - mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); - showDropDown(); + ensureImeVisible(); } } @@ -1084,11 +1085,21 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe /** * Issues a runnable to show the dropdown as soon as possible. * - * @hide internal used only by Search Dialog + * @hide internal used only by SearchDialog */ public void showDropDownAfterLayout() { post(mShowDropDownRunnable); } + + /** + * Ensures that the drop down is not obscuring the IME. + * + * @hide internal used only here and SearchDialog + */ + public void ensureImeVisible() { + mPopup.setInputMethodMode(PopupWindow.INPUT_METHOD_NEEDED); + showDropDown(); + } /** * <p>Displays the drop down on screen.</p> @@ -1285,11 +1296,8 @@ public class AutoCompleteTextView extends EditText implements Filter.FilterListe } } - // Max height available on the screen for a popup. If this AutoCompleteTextView has - // the dropDownAlwaysVisible attribute, and the input method is not currently required, - // we then we ask for the height ignoring any bottom decorations like the input method. - // Otherwise we respect the input method. - boolean ignoreBottomDecorations = mDropDownAlwaysVisible && + // Max height available on the screen for a popup. + boolean ignoreBottomDecorations = mPopup.getInputMethodMode() == PopupWindow.INPUT_METHOD_NOT_NEEDED; final int maxHeight = mPopup.getMaxAvailableHeight( getDropDownAnchorView(), mDropDownVerticalOffset, ignoreBottomDecorations); diff --git a/core/java/android/widget/DatePicker.java b/core/java/android/widget/DatePicker.java index 54f2707..3b9f1de 100644 --- a/core/java/android/widget/DatePicker.java +++ b/core/java/android/widget/DatePicker.java @@ -110,6 +110,8 @@ public class DatePicker extends FrameLayout { * subtract by one to ensure our internal state is always 0-11 */ mMonth = newVal - 1; + // Adjust max day of the month + adjustMaxDay(); if (mOnDateChangedListener != null) { mOnDateChangedListener.onDateChanged(DatePicker.this, mYear, mMonth, mDay); } @@ -121,9 +123,12 @@ public class DatePicker extends FrameLayout { mYearPicker.setOnChangeListener(new OnChangedListener() { public void onChanged(NumberPicker picker, int oldVal, int newVal) { mYear = newVal; + // Adjust max day for leap years if needed + adjustMaxDay(); if (mOnDateChangedListener != null) { mOnDateChangedListener.onDateChanged(DatePicker.this, mYear, mMonth, mDay); } + updateDaySpinner(); } }); @@ -318,4 +323,14 @@ public class DatePicker extends FrameLayout { public int getDayOfMonth() { return mDay; } + + private void adjustMaxDay(){ + Calendar cal = Calendar.getInstance(); + cal.set(Calendar.YEAR, mYear); + cal.set(Calendar.MONTH, mMonth); + int max = cal.getActualMaximum(Calendar.DAY_OF_MONTH); + if (mDay > max) { + mDay = max; + } + } } diff --git a/core/java/android/widget/FastScroller.java b/core/java/android/widget/FastScroller.java index cd965fc..2da777a 100644 --- a/core/java/android/widget/FastScroller.java +++ b/core/java/android/widget/FastScroller.java @@ -26,6 +26,7 @@ import android.graphics.RectF; import android.graphics.drawable.Drawable; import android.os.Handler; import android.os.SystemClock; +import android.util.TypedValue; import android.view.MotionEvent; /** @@ -116,17 +117,19 @@ class FastScroller { mThumbDrawable.setAlpha(ScrollFade.ALPHA_MAX); } - private void useThumbDrawable(Drawable drawable) { + private void useThumbDrawable(Context context, Drawable drawable) { mThumbDrawable = drawable; - mThumbW = 64; //mCurrentThumb.getIntrinsicWidth(); - mThumbH = 52; //mCurrentThumb.getIntrinsicHeight(); + mThumbW = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + 64, context.getResources().getDisplayMetrics()); + mThumbH = (int) TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_DIP, + 52, context.getResources().getDisplayMetrics()); mChangedBounds = true; } private void init(Context context) { // Get both the scrollbar states drawables final Resources res = context.getResources(); - useThumbDrawable(res.getDrawable( + useThumbDrawable(context, res.getDrawable( com.android.internal.R.drawable.scrollbar_handle_accelerated_anim2)); mOverlayDrawable = res.getDrawable( diff --git a/core/java/android/widget/Filter.java b/core/java/android/widget/Filter.java index 7e55c78..d901540 100644 --- a/core/java/android/widget/Filter.java +++ b/core/java/android/widget/Filter.java @@ -46,6 +46,8 @@ public abstract class Filter { private Handler mThreadHandler; private Handler mResultHandler; + private Delayer mDelayer; + private final Object mLock = new Object(); /** @@ -56,6 +58,20 @@ public abstract class Filter { } /** + * Provide an interface that decides how long to delay the message for a given query. Useful + * for heuristics such as posting a delay for the delete key to avoid doing any work while the + * user holds down the delete key. + * + * @param delayer The delayer. + * @hide + */ + public void setDelayer(Delayer delayer) { + synchronized (mLock) { + mDelayer = delayer; + } + } + + /** * <p>Starts an asynchronous filtering operation. Calling this method * cancels all previous non-executed filtering requests and posts a new * filtering request that will be executed later.</p> @@ -85,10 +101,13 @@ public abstract class Filter { public final void filter(CharSequence constraint, FilterListener listener) { synchronized (mLock) { if (mThreadHandler == null) { - HandlerThread thread = new HandlerThread(THREAD_NAME); + HandlerThread thread = new HandlerThread( + THREAD_NAME, android.os.Process.THREAD_PRIORITY_BACKGROUND); thread.start(); mThreadHandler = new RequestHandler(thread.getLooper()); } + + final long delay = (mDelayer == null) ? 0 : mDelayer.getPostingDelay(constraint); Message message = mThreadHandler.obtainMessage(FILTER_TOKEN); @@ -101,7 +120,7 @@ public abstract class Filter { mThreadHandler.removeMessages(FILTER_TOKEN); mThreadHandler.removeMessages(FINISH_TOKEN); - mThreadHandler.sendMessage(message); + mThreadHandler.sendMessageDelayed(message, delay); } } @@ -288,4 +307,17 @@ public abstract class Filter { */ FilterResults results; } + + /** + * @hide + */ + public interface Delayer { + + /** + * @param constraint The constraint passed to {@link Filter#filter(CharSequence)} + * @return The delay that should be used for + * {@link Handler#sendMessageDelayed(android.os.Message, long)} + */ + long getPostingDelay(CharSequence constraint); + } } diff --git a/core/java/android/widget/HorizontalScrollView.java b/core/java/android/widget/HorizontalScrollView.java index f86b37c..46e514c 100644 --- a/core/java/android/widget/HorizontalScrollView.java +++ b/core/java/android/widget/HorizontalScrollView.java @@ -286,18 +286,20 @@ public class HorizontalScrollView extends FrameLayout { return; } - final View child = getChildAt(0); - int width = getMeasuredWidth(); - if (child.getMeasuredWidth() < width) { - final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, mPaddingTop - + mPaddingBottom, lp.height); - width -= mPaddingLeft; - width -= mPaddingRight; - int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); - - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + if (getChildCount() > 0) { + final View child = getChildAt(0); + int width = getMeasuredWidth(); + if (child.getMeasuredWidth() < width) { + final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + int childHeightMeasureSpec = getChildMeasureSpec(heightMeasureSpec, mPaddingTop + + mPaddingBottom, lp.height); + width -= mPaddingLeft; + width -= mPaddingRight; + int childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } } } @@ -636,7 +638,7 @@ public class HorizontalScrollView extends FrameLayout { mTempRect.left = getScrollX() + width; int count = getChildCount(); if (count > 0) { - View view = getChildAt(count - 1); + View view = getChildAt(0); if (mTempRect.left + width > view.getRight()) { mTempRect.left = view.getRight() - width; } @@ -674,7 +676,7 @@ public class HorizontalScrollView extends FrameLayout { if (right) { int count = getChildCount(); if (count > 0) { - View view = getChildAt(count - 1); + View view = getChildAt(0); mTempRect.right = view.getRight(); mTempRect.left = mTempRect.right - width; } @@ -751,9 +753,9 @@ public class HorizontalScrollView extends FrameLayout { if (direction == View.FOCUS_LEFT && getScrollX() < scrollDelta) { scrollDelta = getScrollX(); - } else if (direction == View.FOCUS_RIGHT) { - - int daRight = getChildAt(getChildCount() - 1).getRight(); + } else if (direction == View.FOCUS_RIGHT && getChildCount() > 0) { + + int daRight = getChildAt(0).getRight(); int screenRight = getScrollX() + getWidth(); @@ -975,6 +977,7 @@ public class HorizontalScrollView extends FrameLayout { * @return The scroll delta. */ protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) { + if (getChildCount() == 0) return 0; int width = getWidth(); int screenLeft = getScrollX(); @@ -1008,7 +1011,7 @@ public class HorizontalScrollView extends FrameLayout { } // make sure we aren't scrolling beyond the end of our content - int right = getChildAt(getChildCount() - 1).getRight(); + int right = getChildAt(0).getRight(); int distanceToRight = right - screenRight; scrollXDelta = Math.min(scrollXDelta, distanceToRight); @@ -1148,27 +1151,29 @@ public class HorizontalScrollView extends FrameLayout { * which means we want to scroll towards the left. */ public void fling(int velocityX) { - int width = getWidth() - mPaddingRight - mPaddingLeft; - int right = getChildAt(0).getWidth(); - - mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0, right - width, 0, 0); - - final boolean movingRight = velocityX > 0; - - View newFocused = findFocusableViewInMyBounds(movingRight, - mScroller.getFinalX(), findFocus()); - - if (newFocused == null) { - newFocused = this; - } - - if (newFocused != findFocus() - && newFocused.requestFocus(movingRight ? View.FOCUS_RIGHT : View.FOCUS_LEFT)) { - mScrollViewMovedFocus = true; - mScrollViewMovedFocus = false; + if (getChildCount() > 0) { + int width = getWidth() - mPaddingRight - mPaddingLeft; + int right = getChildAt(0).getWidth(); + + mScroller.fling(mScrollX, mScrollY, velocityX, 0, 0, right - width, 0, 0); + + final boolean movingRight = velocityX > 0; + + View newFocused = findFocusableViewInMyBounds(movingRight, + mScroller.getFinalX(), findFocus()); + + if (newFocused == null) { + newFocused = this; + } + + if (newFocused != findFocus() + && newFocused.requestFocus(movingRight ? View.FOCUS_RIGHT : View.FOCUS_LEFT)) { + mScrollViewMovedFocus = true; + mScrollViewMovedFocus = false; + } + + invalidate(); } - - invalidate(); } /** diff --git a/core/java/android/widget/PopupWindow.java b/core/java/android/widget/PopupWindow.java index 0c2cd55..90fbb77 100644 --- a/core/java/android/widget/PopupWindow.java +++ b/core/java/android/widget/PopupWindow.java @@ -27,7 +27,6 @@ import android.view.WindowManager; import android.view.Gravity; import android.view.ViewGroup; import android.view.ViewTreeObserver; -import android.view.WindowManagerImpl; import android.view.ViewTreeObserver.OnScrollChangedListener; import android.view.View.OnTouchListener; import android.graphics.PixelFormat; @@ -82,6 +81,7 @@ public class PopupWindow { private View mPopupView; private boolean mFocusable; private int mInputMethodMode = INPUT_METHOD_FROM_FOCUSABLE; + private int mSoftInputMode; private boolean mTouchable = true; private boolean mOutsideTouchable = false; private boolean mClippingEnabled = true; @@ -446,6 +446,30 @@ public class PopupWindow { public void setInputMethodMode(int mode) { mInputMethodMode = mode; } + + /** + * Sets the operating mode for the soft input area. + * + * @param mode The desired mode, see + * {@link android.view.WindowManager.LayoutParams#softInputMode} + * for the full list + * + * @see android.view.WindowManager.LayoutParams#softInputMode + * @see #getSoftInputMode() + */ + public void setSoftInputMode(int mode) { + mSoftInputMode = mode; + } + + /** + * Returns the current value in {@link #setSoftInputMode(int)}. + * + * @see #setSoftInputMode(int) + * @see android.view.WindowManager.LayoutParams#softInputMode + */ + public int getSoftInputMode() { + return mSoftInputMode; + } /** * <p>Indicates whether the popup window receives touch events.</p> @@ -822,7 +846,7 @@ public class PopupWindow { p.flags = computeFlags(p.flags); p.type = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL; p.token = token; - p.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; + p.softInputMode = mSoftInputMode; p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); return p; diff --git a/core/java/android/widget/RelativeLayout.java b/core/java/android/widget/RelativeLayout.java index e62dda5..24c0e2a 100644 --- a/core/java/android/widget/RelativeLayout.java +++ b/core/java/android/widget/RelativeLayout.java @@ -1295,11 +1295,8 @@ public class RelativeLayout extends ViewGroup { if (rule > 0) { // The node this node depends on final Node dependency = keyNodes.get(rule); - if (dependency == node) { - throw new IllegalStateException("A view cannot have a dependency" + - " on itself"); - } - if (dependency == null) { + // Skip unknowns and self dependencies + if (dependency == null || dependency == node) { continue; } // Add the current node as a dependent diff --git a/core/java/android/widget/ScrollView.java b/core/java/android/widget/ScrollView.java index 90e1242..703cd8e 100644 --- a/core/java/android/widget/ScrollView.java +++ b/core/java/android/widget/ScrollView.java @@ -20,8 +20,6 @@ import android.content.Context; import android.content.res.TypedArray; import android.graphics.Rect; import android.util.AttributeSet; -import android.util.Config; -import android.util.Log; import android.view.FocusFinder; import android.view.KeyEvent; import android.view.MotionEvent; @@ -54,7 +52,6 @@ import java.util.List; */ public class ScrollView extends FrameLayout { static final String TAG = "ScrollView"; - static final boolean localLOGV = false || Config.LOGV; static final int ANIMATED_SCROLL_GAP = 250; @@ -287,18 +284,21 @@ public class ScrollView extends FrameLayout { return; } - final View child = getChildAt(0); - int height = getMeasuredHeight(); - if (child.getMeasuredHeight() < height) { - final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams(); - - int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, mPaddingLeft - + mPaddingRight, lp.width); - height -= mPaddingTop; - height -= mPaddingBottom; - int childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); - - child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + if (getChildCount() > 0) { + final View child = getChildAt(0); + int height = getMeasuredHeight(); + if (child.getMeasuredHeight() < height) { + final FrameLayout.LayoutParams lp = (LayoutParams) child.getLayoutParams(); + + int childWidthMeasureSpec = getChildMeasureSpec(widthMeasureSpec, mPaddingLeft + + mPaddingRight, lp.width); + height -= mPaddingTop; + height -= mPaddingBottom; + int childHeightMeasureSpec = + MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); + + child.measure(childWidthMeasureSpec, childHeightMeasureSpec); + } } } @@ -756,13 +756,14 @@ public class ScrollView extends FrameLayout { if (direction == View.FOCUS_UP && getScrollY() < scrollDelta) { scrollDelta = getScrollY(); } else if (direction == View.FOCUS_DOWN) { - - int daBottom = getChildAt(getChildCount() - 1).getBottom(); - - int screenBottom = getScrollY() + getHeight(); - - if (daBottom - screenBottom < maxJump) { - scrollDelta = daBottom - screenBottom; + if (getChildCount() > 0) { + int daBottom = getChildAt(0).getBottom(); + + int screenBottom = getScrollY() + getHeight(); + + if (daBottom - screenBottom < maxJump) { + scrollDelta = daBottom - screenBottom; + } } } if (scrollDelta == 0) { @@ -830,16 +831,12 @@ public class ScrollView extends FrameLayout { public final void smoothScrollBy(int dx, int dy) { long duration = AnimationUtils.currentAnimationTimeMillis() - mLastScroll; if (duration > ANIMATED_SCROLL_GAP) { - if (localLOGV) Log.v(TAG, "Smooth scroll: mScrollY=" + mScrollY - + " dy=" + dy); mScroller.startScroll(mScrollX, mScrollY, dx, dy); invalidate(); } else { if (!mScroller.isFinished()) { mScroller.abortAnimation(); } - if (localLOGV) Log.v(TAG, "Immediate scroll: mScrollY=" + mScrollY - + " dy=" + dy); scrollBy(dx, dy); } mLastScroll = AnimationUtils.currentAnimationTimeMillis(); @@ -922,9 +919,6 @@ public class ScrollView extends FrameLayout { View child = getChildAt(0); mScrollX = clamp(x, getWidth() - mPaddingRight - mPaddingLeft, child.getWidth()); mScrollY = clamp(y, getHeight() - mPaddingBottom - mPaddingTop, child.getHeight()); - if (localLOGV) Log.v(TAG, "mScrollY=" + mScrollY + " y=" + y - + " height=" + this.getHeight() - + " child height=" + child.getHeight()); } else { mScrollX = x; mScrollY = y; @@ -986,6 +980,7 @@ public class ScrollView extends FrameLayout { * @return The scroll delta. */ protected int computeScrollDeltaToGetChildRectOnScreen(Rect rect) { + if (getChildCount() == 0) return 0; int height = getHeight(); int screenTop = getScrollY(); @@ -1005,9 +1000,6 @@ public class ScrollView extends FrameLayout { int scrollYDelta = 0; - if (localLOGV) Log.v(TAG, "child=" + rect.toShortString() - + " screenTop=" + screenTop + " screenBottom=" + screenBottom - + " height=" + height); if (rect.bottom > screenBottom && rect.top > screenTop) { // need to move down to get it in view: move down just enough so // that the entire rectangle is in view (or at least the first @@ -1022,10 +1014,8 @@ public class ScrollView extends FrameLayout { } // make sure we aren't scrolling beyond the end of our content - int bottom = getChildAt(getChildCount() - 1).getBottom(); + int bottom = getChildAt(0).getBottom(); int distanceToBottom = bottom - screenBottom; - if (localLOGV) Log.v(TAG, "scrollYDelta=" + scrollYDelta - + " distanceToBottom=" + distanceToBottom); scrollYDelta = Math.min(scrollYDelta, distanceToBottom); } else if (rect.top < screenTop && rect.bottom < screenBottom) { @@ -1164,26 +1154,28 @@ public class ScrollView extends FrameLayout { * which means we want to scroll towards the top. */ public void fling(int velocityY) { - int height = getHeight() - mPaddingBottom - mPaddingTop; - int bottom = getChildAt(0).getHeight(); - - mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, bottom - height); - - final boolean movingDown = velocityY > 0; - - View newFocused = - findFocusableViewInMyBounds(movingDown, mScroller.getFinalY(), findFocus()); - if (newFocused == null) { - newFocused = this; - } - - if (newFocused != findFocus() - && newFocused.requestFocus(movingDown ? View.FOCUS_DOWN : View.FOCUS_UP)) { - mScrollViewMovedFocus = true; - mScrollViewMovedFocus = false; + if (getChildCount() > 0) { + int height = getHeight() - mPaddingBottom - mPaddingTop; + int bottom = getChildAt(0).getHeight(); + + mScroller.fling(mScrollX, mScrollY, 0, velocityY, 0, 0, 0, bottom - height); + + final boolean movingDown = velocityY > 0; + + View newFocused = + findFocusableViewInMyBounds(movingDown, mScroller.getFinalY(), findFocus()); + if (newFocused == null) { + newFocused = this; + } + + if (newFocused != findFocus() + && newFocused.requestFocus(movingDown ? View.FOCUS_DOWN : View.FOCUS_UP)) { + mScrollViewMovedFocus = true; + mScrollViewMovedFocus = false; + } + + invalidate(); } - - invalidate(); } /** diff --git a/core/java/android/widget/Spinner.java b/core/java/android/widget/Spinner.java index 80d688e..bcddca1 100644 --- a/core/java/android/widget/Spinner.java +++ b/core/java/android/widget/Spinner.java @@ -24,7 +24,6 @@ import android.content.DialogInterface.OnClickListener; import android.content.res.TypedArray; import android.database.DataSetObserver; import android.util.AttributeSet; -import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; @@ -40,6 +39,7 @@ import android.view.ViewGroup; public class Spinner extends AbsSpinner implements OnClickListener { private CharSequence mPrompt; + private AlertDialog mPopup; public Spinner(Context context) { this(context, null); @@ -78,6 +78,16 @@ public class Spinner extends AbsSpinner implements OnClickListener { } } + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + if (mPopup != null && mPopup.isShowing()) { + mPopup.dismiss(); + mPopup = null; + } + } + /** * <p>A spinner does not support item click events. Calling this method * will raise an exception.</p> @@ -244,7 +254,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { if (mPrompt != null) { builder.setTitle(mPrompt); } - builder.setSingleChoiceItems(adapter, getSelectedItemPosition(), this).show(); + mPopup = builder.setSingleChoiceItems(adapter, getSelectedItemPosition(), this).show(); } return handled; @@ -253,6 +263,7 @@ public class Spinner extends AbsSpinner implements OnClickListener { public void onClick(DialogInterface dialog, int which) { setSelection(which); dialog.dismiss(); + mPopup = null; } /** diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index d8ed4f0..3fab692 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -2443,7 +2443,13 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener } if (ss.error != null) { - setError(ss.error); + final CharSequence error = ss.error; + // Display the error later, after the first layout pass + post(new Runnable() { + public void run() { + setError(error); + } + }); } } @@ -3263,7 +3269,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener final TextView err = (TextView) inflater.inflate(com.android.internal.R.layout.textview_hint, null); - mPopup = new ErrorPopup(err, 200, 50); + final float scale = getResources().getDisplayMetrics().density; + mPopup = new ErrorPopup(err, (int) (200 * scale + 0.5f), + (int) (50 * scale + 0.5f)); mPopup.setFocusable(false); // The user is entering text, so the input method is needed. We // don't want the popup to be displayed on top of it. @@ -3317,11 +3325,12 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * The "25" is the distance between the point and the right edge * of the background */ + final float scale = getResources().getDisplayMetrics().density; final Drawables dr = mDrawables; return getWidth() - mPopup.getWidth() - getPaddingRight() - - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + 25; + - (dr != null ? dr.mDrawableSizeRight : 0) / 2 + (int) (25 * scale + 0.5f); } /** diff --git a/core/java/android/widget/Toast.java b/core/java/android/widget/Toast.java index 670692f..df957ac 100644 --- a/core/java/android/widget/Toast.java +++ b/core/java/android/widget/Toast.java @@ -65,6 +65,7 @@ public class Toast { */ public static final int LENGTH_LONG = 1; + final Handler mHandler = new Handler(); final Context mContext; final TN mTN; int mDuration; @@ -84,7 +85,7 @@ public class Toast { */ public Toast(Context context) { mContext = context; - mTN = new TN(context); + mTN = new TN(); mY = context.getResources().getDimensionPixelSize( com.android.internal.R.dimen.toast_y_offset); } @@ -229,7 +230,8 @@ public class Toast { public static Toast makeText(Context context, CharSequence text, int duration) { Toast result = new Toast(context); - LayoutInflater inflate = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); + LayoutInflater inflate = (LayoutInflater) + context.getSystemService(Context.LAYOUT_INFLATER_SERVICE); View v = inflate.inflate(com.android.internal.R.layout.transient_notification, null); TextView tv = (TextView)v.findViewById(com.android.internal.R.id.message); tv.setText(text); @@ -286,8 +288,7 @@ public class Toast { private static INotificationManager sService; - static private INotificationManager getService() - { + static private INotificationManager getService() { if (sService != null) { return sService; } @@ -295,28 +296,42 @@ public class Toast { return sService; } - private class TN extends ITransientNotification.Stub - { - TN(Context context) - { + private class TN extends ITransientNotification.Stub { + final Runnable mShow = new Runnable() { + public void run() { + handleShow(); + } + }; + + final Runnable mHide = new Runnable() { + public void run() { + handleHide(); + } + }; + + private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); + + WindowManagerImpl mWM; + + TN() { // XXX This should be changed to use a Dialog, with a Theme.Toast // defined that sets up the layout params appropriately. - mParams.height = WindowManager.LayoutParams.WRAP_CONTENT; - mParams.width = WindowManager.LayoutParams.WRAP_CONTENT; - mParams.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE + final WindowManager.LayoutParams params = mParams; + params.height = WindowManager.LayoutParams.WRAP_CONTENT; + params.width = WindowManager.LayoutParams.WRAP_CONTENT; + params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE | WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; - mParams.format = PixelFormat.TRANSLUCENT; - mParams.windowAnimations = com.android.internal.R.style.Animation_Toast; - mParams.type = WindowManager.LayoutParams.TYPE_TOAST; - mParams.setTitle("Toast"); + params.format = PixelFormat.TRANSLUCENT; + params.windowAnimations = com.android.internal.R.style.Animation_Toast; + params.type = WindowManager.LayoutParams.TYPE_TOAST; + params.setTitle("Toast"); } /** * schedule handleShow into the right thread */ - public void show() - { + public void show() { if (localLOGV) Log.v(TAG, "SHOW: " + this); mHandler.post(mShow); } @@ -324,14 +339,12 @@ public class Toast { /** * schedule handleHide into the right thread */ - public void hide() - { + public void hide() { if (localLOGV) Log.v(TAG, "HIDE: " + this); mHandler.post(mHide); } - public void handleShow() - { + public void handleShow() { if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView + " mNextView=" + mNextView); if (mView != mNextView) { @@ -361,8 +374,7 @@ public class Toast { } } - public void handleHide() - { + public void handleHide() { if (localLOGV) Log.v(TAG, "HANDLE HIDE: " + this + " mView=" + mView); if (mView != null) { // note: checking parent() just to make sure the view has @@ -377,24 +389,5 @@ public class Toast { mView = null; } } - - Runnable mShow = new Runnable() { - public void run() { - handleShow(); - } - }; - - Runnable mHide = new Runnable() { - public void run() { - handleHide(); - } - }; - - private final WindowManager.LayoutParams mParams = new WindowManager.LayoutParams(); - - WindowManagerImpl mWM; } - - final Handler mHandler = new Handler(); } - |