summaryrefslogtreecommitdiffstats
path: root/core/java/android
diff options
context:
space:
mode:
authorJean-Baptiste Queru <jbq@google.com>2009-07-29 14:25:07 -0700
committerJean-Baptiste Queru <jbq@google.com>2009-07-29 14:25:07 -0700
commita8675f67e33bc7337d148358783b0fd138b501ff (patch)
tree71fb9d10330ef9161b3ead71d01074b3ef9e53ba /core/java/android
parentcf4550c3198d6b3d92cdc52707fe70d7cc0caa9f (diff)
downloadframeworks_base-a8675f67e33bc7337d148358783b0fd138b501ff.zip
frameworks_base-a8675f67e33bc7337d148358783b0fd138b501ff.tar.gz
frameworks_base-a8675f67e33bc7337d148358783b0fd138b501ff.tar.bz2
donut snapshot
Diffstat (limited to 'core/java/android')
-rw-r--r--core/java/android/app/Activity.java84
-rw-r--r--core/java/android/app/ActivityManagerNative.java89
-rw-r--r--core/java/android/app/ActivityThread.java147
-rw-r--r--core/java/android/app/ApplicationContext.java31
-rw-r--r--core/java/android/app/ApplicationThreadNative.java7
-rw-r--r--core/java/android/app/Dialog.java64
-rw-r--r--core/java/android/app/IActivityController.aidl55
-rw-r--r--core/java/android/app/IActivityManager.java17
-rw-r--r--core/java/android/app/IActivityWatcher.aidl39
-rw-r--r--core/java/android/app/IApplicationThread.java2
-rw-r--r--core/java/android/app/ISearchManager.aidl6
-rw-r--r--core/java/android/app/SearchDialog.java187
-rw-r--r--core/java/android/app/SearchManager.java128
-rw-r--r--core/java/android/app/SuggestionsAdapter.java287
-rwxr-xr-xcore/java/android/appwidget/AppWidgetProvider.java8
-rw-r--r--core/java/android/appwidget/AppWidgetProviderInfo.java3
-rw-r--r--core/java/android/backup/BackupManager.java12
-rw-r--r--core/java/android/content/AbstractTableMerger.java49
-rw-r--r--core/java/android/content/ContentProvider.java136
-rw-r--r--core/java/android/content/DialogInterface.java15
-rw-r--r--core/java/android/content/Intent.java124
-rw-r--r--core/java/android/content/SyncStorageEngine.java1
-rw-r--r--core/java/android/content/pm/ApplicationInfo.java17
-rw-r--r--core/java/android/content/pm/IPackageManager.aidl2
-rw-r--r--core/java/android/content/pm/PackageParser.java202
-rw-r--r--core/java/android/content/pm/PathPermission.java68
-rw-r--r--core/java/android/content/pm/ProviderInfo.java12
-rw-r--r--core/java/android/content/res/CompatibilityInfo.java334
-rw-r--r--core/java/android/content/res/Configuration.java51
-rw-r--r--core/java/android/content/res/Resources.java68
-rw-r--r--core/java/android/database/sqlite/SQLiteDatabase.java3
-rwxr-xr-xcore/java/android/gesture/Gesture.java24
-rw-r--r--core/java/android/gesture/package.html2
-rw-r--r--core/java/android/net/Uri.java50
-rw-r--r--core/java/android/os/BatteryStats.java22
-rw-r--r--core/java/android/os/Process.java12
-rw-r--r--core/java/android/os/RemoteCallbackList.java24
-rw-r--r--core/java/android/provider/Settings.java25
-rw-r--r--core/java/android/provider/Telephony.java15
-rw-r--r--core/java/android/server/search/SearchDialogWrapper.java387
-rw-r--r--core/java/android/server/search/SearchManagerService.java384
-rw-r--r--core/java/android/server/search/SearchableInfo.java23
-rw-r--r--core/java/android/server/search/Searchables.java32
-rwxr-xr-xcore/java/android/speech/tts/ITts.aidl28
-rwxr-xr-xcore/java/android/speech/tts/ITtsCallback.aidl2
-rwxr-xr-x[-rw-r--r--]core/java/android/speech/tts/TextToSpeech.java500
-rw-r--r--core/java/android/text/Html.java107
-rw-r--r--core/java/android/util/DisplayMetrics.java135
-rw-r--r--core/java/android/util/TypedValue.java12
-rw-r--r--core/java/android/view/Display.java27
-rw-r--r--core/java/android/view/Surface.java23
-rw-r--r--core/java/android/view/SurfaceView.java37
-rw-r--r--core/java/android/view/ViewRoot.java49
-rw-r--r--core/java/android/view/Window.java8
-rw-r--r--core/java/android/view/WindowManager.java18
-rw-r--r--core/java/android/webkit/GearsPermissionsManager.java240
-rw-r--r--core/java/android/webkit/LoadListener.java32
-rw-r--r--core/java/android/webkit/MimeTypeMap.java1
-rw-r--r--core/java/android/webkit/URLUtil.java2
-rw-r--r--core/java/android/webkit/WebSettings.java23
-rw-r--r--core/java/android/widget/AbsListView.java2
-rwxr-xr-xcore/java/android/widget/AppSecurityPermissions.java12
-rw-r--r--core/java/android/widget/AutoCompleteTextView.java24
-rw-r--r--core/java/android/widget/DatePicker.java15
-rw-r--r--core/java/android/widget/FastScroller.java11
-rw-r--r--core/java/android/widget/Filter.java36
-rw-r--r--core/java/android/widget/HorizontalScrollView.java81
-rw-r--r--core/java/android/widget/PopupWindow.java28
-rw-r--r--core/java/android/widget/RelativeLayout.java7
-rw-r--r--core/java/android/widget/ScrollView.java100
-rw-r--r--core/java/android/widget/Spinner.java15
-rw-r--r--core/java/android/widget/TextView.java15
-rw-r--r--core/java/android/widget/Toast.java77
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>&lt;manifest xmlns:android=&quot;...&quot;
+ * package=&quot;<b>com.google.marvin.compass</b>&quot;&gt;</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();
}
-