diff options
author | Jean-Baptiste Queru <jbq@google.com> | 2009-11-12 18:45:53 -0800 |
---|---|---|
committer | Jean-Baptiste Queru <jbq@google.com> | 2009-11-13 13:53:39 -0800 |
commit | 9db3d07b9620b4269ab33f78604a36327e536ce1 (patch) | |
tree | 41e294f34b9695187af098cd42167489fb0c8fb0 /core/java/android/app | |
parent | 6c63ee4fc4acae4bbbbd2a49e0a68206221f0de0 (diff) | |
download | frameworks_base-9db3d07b9620b4269ab33f78604a36327e536ce1.zip frameworks_base-9db3d07b9620b4269ab33f78604a36327e536ce1.tar.gz frameworks_base-9db3d07b9620b4269ab33f78604a36327e536ce1.tar.bz2 |
eclair snapshot
Diffstat (limited to 'core/java/android/app')
28 files changed, 2897 insertions, 705 deletions
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index f2905a7..49ebce3 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -24,6 +24,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.IIntentSender; +import android.content.IntentSender; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.res.Configuration; @@ -34,6 +35,7 @@ import android.graphics.Canvas; import android.graphics.drawable.Drawable; import android.media.AudioManager; import android.net.Uri; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.IBinder; @@ -788,8 +790,8 @@ public class Activity extends ContextThemeWrapper * @see #onPostCreate */ protected void onCreate(Bundle savedInstanceState) { - mVisibleFromClient = mWindow.getWindowStyle().getBoolean( - com.android.internal.R.styleable.Window_windowNoDisplay, true); + mVisibleFromClient = !mWindow.getWindowStyle().getBoolean( + com.android.internal.R.styleable.Window_windowNoDisplay, false); mCalled = true; } @@ -1752,8 +1754,17 @@ public class Activity extends ContextThemeWrapper * * <p>If the focused view didn't want this event, this method is called. * - * <p>The default implementation handles KEYCODE_BACK to stop the activity - * and go back, and other default key handling if configured with {@link #setDefaultKeyMode}. + * <p>The default implementation takes care of {@link KeyEvent#KEYCODE_BACK} + * by calling {@link #onBackPressed()}, though the behavior varies based + * on the application compatibility mode: for + * {@link android.os.Build.VERSION_CODES#ECLAIR} or later applications, + * it will set up the dispatch to call {@link #onKeyUp} where the action + * will be performed; for earlier applications, it will perform the + * action immediately in on-down, as those versions of the platform + * behaved. + * + * <p>Other additional default key handling may be performed + * if configured with {@link #setDefaultKeyMode}. * * @return Return <code>true</code> to prevent this event from being propagated * further, or <code>false</code> to indicate that you have not handled @@ -1762,16 +1773,24 @@ public class Activity extends ContextThemeWrapper * @see android.view.KeyEvent */ public boolean onKeyDown(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK && event.getRepeatCount() == 0) { - finish(); + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (getApplicationInfo().targetSdkVersion + >= Build.VERSION_CODES.ECLAIR) { + event.startTracking(); + } else { + onBackPressed(); + } return true; } if (mDefaultKeyMode == DEFAULT_KEYS_DISABLE) { return false; } else if (mDefaultKeyMode == DEFAULT_KEYS_SHORTCUT) { - return getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, - keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE); + if (getWindow().performPanelShortcut(Window.FEATURE_OPTIONS_PANEL, + keyCode, event, Menu.FLAG_ALWAYS_PERFORM_CLOSE)) { + return true; + } + return false; } else { // Common code for DEFAULT_KEYS_DIALER & DEFAULT_KEYS_SEARCH_* boolean clearSpannable = false; @@ -1780,8 +1799,8 @@ public class Activity extends ContextThemeWrapper clearSpannable = true; handled = false; } else { - handled = TextKeyListener.getInstance().onKeyDown(null, mDefaultKeySsb, - keyCode, event); + handled = TextKeyListener.getInstance().onKeyDown( + null, mDefaultKeySsb, keyCode, event); if (handled && mDefaultKeySsb.length() > 0) { // something useable has been typed - dispatch it now. @@ -1813,11 +1832,23 @@ public class Activity extends ContextThemeWrapper } /** + * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent) + * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle + * the event). + */ + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + return false; + } + + /** * Called when a key was released and not handled by any of the views * inside of the activity. So, for example, key presses while the cursor * is inside a TextView will not trigger the event (unless it is a navigation * to another object) because TextView handles its own key presses. * + * <p>The default implementation handles KEYCODE_BACK to stop the activity + * and go back. + * * @return Return <code>true</code> to prevent this event from being propagated * further, or <code>false</code> to indicate that you have not handled * this event and it should continue to be propagated. @@ -1825,6 +1856,14 @@ public class Activity extends ContextThemeWrapper * @see KeyEvent */ public boolean onKeyUp(int keyCode, KeyEvent event) { + if (getApplicationInfo().targetSdkVersion + >= Build.VERSION_CODES.ECLAIR) { + if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() + && !event.isCanceled()) { + onBackPressed(); + return true; + } + } return false; } @@ -1838,6 +1877,15 @@ public class Activity extends ContextThemeWrapper } /** + * Called when the activity has detected the user's press of the back + * key. The default implementation simply finishes the current activity, + * but you can override this to do whatever you want. + */ + public void onBackPressed() { + finish(); + } + + /** * Called when a touch screen event was not handled by any of the views * under it. This is most useful to process touch events that happen * outside of your window bounds, where there is no view to receive it. @@ -1909,9 +1957,10 @@ public class Activity extends ContextThemeWrapper /** * Called when the current {@link Window} of the activity gains or loses * focus. This is the best indicator of whether this activity is visible - * to the user. + * to the user. The default implementation clears the key tracking + * state, so should always be called. * - * <p>Note that this provides information what global focus state, which + * <p>Note that this provides information about global focus state, which * is managed independently of activity lifecycles. As such, while focus * changes will generally have some relation to lifecycle changes (an * activity that is stopped will not generally get window focus), you @@ -1930,11 +1979,32 @@ public class Activity extends ContextThemeWrapper * * @see #hasWindowFocus() * @see #onResume + * @see View#onWindowFocusChanged(boolean) */ public void onWindowFocusChanged(boolean hasFocus) { } /** + * Called when the main window associated with the activity has been + * attached to the window manager. + * See {@link View#onAttachedToWindow() View.onAttachedToWindow()} + * for more information. + * @see View#onAttachedToWindow + */ + public void onAttachedToWindow() { + } + + /** + * Called when the main window associated with the activity has been + * detached from the window manager. + * See {@link View#onDetachedFromWindow() View.onDetachedFromWindow()} + * for more information. + * @see View#onDetachedFromWindow + */ + public void onDetachedFromWindow() { + } + + /** * Returns true if this activity's <em>main</em> window currently has window focus. * Note that this is not the same as the view itself having focus. * @@ -1964,10 +2034,14 @@ public class Activity extends ContextThemeWrapper */ public boolean dispatchKeyEvent(KeyEvent event) { onUserInteraction(); - if (getWindow().superDispatchKeyEvent(event)) { + Window win = getWindow(); + if (win.superDispatchKeyEvent(event)) { return true; } - return event.dispatch(this); + View decor = mDecor; + if (decor == null) decor = win.getDecorView(); + return event.dispatch(this, decor != null + ? decor.getKeyDispatcherState() : null, this); } /** @@ -2394,6 +2468,7 @@ public class Activity extends ContextThemeWrapper * * @param id The id of the managed dialog. * + * @see Dialog * @see #onCreateDialog(int) * @see #onPrepareDialog(int, Dialog) * @see #dismissDialog(int) @@ -2479,16 +2554,17 @@ public class Activity extends ContextThemeWrapper /** * This hook is called when the user signals the desire to start a search. * - * <p>You can use this function as a simple way to launch the search UI, in response to a - * menu item, search button, or other widgets within your activity. Unless overidden, - * calling this function is the same as calling: - * <p>The default implementation simply calls - * {@link #startSearch startSearch(null, false, null, false)}, launching a local search. + * <p>You can use this function as a simple way to launch the search UI, in response to a + * menu item, search button, or other widgets within your activity. Unless overidden, + * calling this function is the same as calling + * {@link #startSearch startSearch(null, false, null, false)}, which launches + * search for the current activity as specified in its manifest, see {@link SearchManager}. * * <p>You can override this function to force global search, e.g. in response to a dedicated * search key, or to block search entirely (by simply returning false). * - * @return Returns true if search launched, false if activity blocks it + * @return Returns {@code true} if search launched, and {@code false} if activity blocks it. + * The default implementation always returns {@code true}. * * @see android.app.SearchManager */ @@ -2535,6 +2611,21 @@ public class Activity extends ContextThemeWrapper } /** + * Similar to {@link #startSearch}, but actually fires off the search query after invoking + * the search dialog. Made available for testing purposes. + * + * @param query The query to trigger. If empty, the request will be ignored. + * @param appSearchData An application can insert application-specific + * context here, in order to improve quality or specificity of its own + * searches. This data will be returned with SEARCH intent(s). Null if + * no extra data is required. + */ + public void triggerSearch(String query, Bundle appSearchData) { + ensureSearchManager(); + mSearchManager.triggerSearch(query, getComponentName(), appSearchData); + } + + /** * Request that key events come to this activity. Use this if your * activity has no views with focus, but the activity still wants * a chance to process key events. @@ -2608,10 +2699,8 @@ public class Activity extends ContextThemeWrapper } @Override - protected void onApplyThemeResource(Resources.Theme theme, - int resid, - boolean first) - { + protected void onApplyThemeResource(Resources.Theme theme, int resid, + boolean first) { if (mParent == null) { super.onApplyThemeResource(theme, resid, first); } else { @@ -2682,6 +2771,68 @@ public class Activity extends ContextThemeWrapper } /** + * Like {@link #startActivityForResult(Intent, int)}, but allowing you + * to use a IntentSender to describe the activity to be started. If + * the IntentSender is for an activity, that activity will be started + * as if you had called the regular {@link #startActivityForResult(Intent, int)} + * here; otherwise, its associated action will be executed (such as + * sending a broadcast) as if you had called + * {@link IntentSender#sendIntent IntentSender.sendIntent} on it. + * + * @param intent The IntentSender to launch. + * @param requestCode If >= 0, this code will be returned in + * onActivityResult() when the activity exits. + * @param fillInIntent If non-null, this will be provided as the + * intent parameter to {@link IntentSender#sendIntent}. + * @param flagsMask Intent flags in the original IntentSender that you + * would like to change. + * @param flagsValues Desired values for any bits set in + * <var>flagsMask</var> + * @param extraFlags Always set to 0. + */ + public void startIntentSenderForResult(IntentSender intent, int requestCode, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) + throws IntentSender.SendIntentException { + if (mParent == null) { + startIntentSenderForResultInner(intent, requestCode, fillInIntent, + flagsMask, flagsValues, this); + } else { + mParent.startIntentSenderFromChild(this, intent, requestCode, + fillInIntent, flagsMask, flagsValues, extraFlags); + } + } + + private void startIntentSenderForResultInner(IntentSender intent, int requestCode, + Intent fillInIntent, int flagsMask, int flagsValues, Activity activity) + throws IntentSender.SendIntentException { + try { + String resolvedType = null; + if (fillInIntent != null) { + resolvedType = fillInIntent.resolveTypeIfNeeded(getContentResolver()); + } + int result = ActivityManagerNative.getDefault() + .startActivityIntentSender(mMainThread.getApplicationThread(), intent, + fillInIntent, resolvedType, mToken, activity.mEmbeddedID, + requestCode, flagsMask, flagsValues); + if (result == IActivityManager.START_CANCELED) { + throw new IntentSender.SendIntentException(); + } + Instrumentation.checkStartActivityResult(result, null); + } catch (RemoteException e) { + } + if (requestCode >= 0) { + // If this start is requesting a result, we can avoid making + // the activity visible until the result is received. Setting + // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the + // activity hidden during this time, to avoid flickering. + // This can only be done when a result is requested because + // that guarantees we will get information back when the + // activity is finished, no matter what happens to it. + mStartedActivity = true; + } + } + + /** * Launch a new activity. You will not receive any information about when * the activity exits. This implementation overrides the base version, * providing information about @@ -2705,6 +2856,28 @@ public class Activity extends ContextThemeWrapper } /** + * Like {@link #startActivity(Intent)}, but taking a IntentSender + * to start; see + * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} + * for more information. + * + * @param intent The IntentSender to launch. + * @param fillInIntent If non-null, this will be provided as the + * intent parameter to {@link IntentSender#sendIntent}. + * @param flagsMask Intent flags in the original IntentSender that you + * would like to change. + * @param flagsValues Desired values for any bits set in + * <var>flagsMask</var> + * @param extraFlags Always set to 0. + */ + public void startIntentSender(IntentSender intent, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) + throws IntentSender.SendIntentException { + startIntentSenderForResult(intent, -1, fillInIntent, flagsMask, + flagsValues, extraFlags); + } + + /** * A special variation to launch an activity only if a new activity * instance is needed to handle the given Intent. In other words, this is * just like {@link #startActivityForResult(Intent, int)} except: if you are @@ -2825,6 +2998,37 @@ public class Activity extends ContextThemeWrapper } /** + * Like {@link #startActivityFromChild(Activity, Intent, int)}, but + * taking a IntentSender; see + * {@link #startIntentSenderForResult(IntentSender, int, Intent, int, int, int)} + * for more information. + */ + public void startIntentSenderFromChild(Activity child, IntentSender intent, + int requestCode, Intent fillInIntent, int flagsMask, int flagsValues, + int extraFlags) + throws IntentSender.SendIntentException { + startIntentSenderForResultInner(intent, requestCode, fillInIntent, + flagsMask, flagsValues, child); + } + + /** + * Call immediately after one of the flavors of {@link #startActivity(Intent)} + * or {@link #finish} to specify an explicit transition animation to + * perform next. + * @param enterAnim A resource ID of the animation resource to use for + * the incoming activity. Use 0 for no animation. + * @param exitAnim A resource ID of the animation resource to use for + * the outgoing activity. Use 0 for no animation. + */ + public void overridePendingTransition(int enterAnim, int exitAnim) { + try { + ActivityManagerNative.getDefault().overridePendingTransition( + mToken, getPackageName(), enterAnim, exitAnim); + } catch (RemoteException e) { + } + } + + /** * Call this to set the result that your activity will return to its * caller. * @@ -3255,7 +3459,7 @@ public class Activity extends ContextThemeWrapper throw new IllegalArgumentException("no ident"); } } - mSearchManager.setIdent(ident); + mSearchManager.setIdent(ident, getComponentName()); } @Override diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java index 07520c9d..d709deb 100644 --- a/core/java/android/app/ActivityManager.java +++ b/core/java/android/app/ActivityManager.java @@ -22,10 +22,12 @@ import android.content.Intent; import android.content.pm.ConfigurationInfo; import android.content.pm.IPackageDataObserver; import android.graphics.Bitmap; +import android.os.Debug; import android.os.RemoteException; import android.os.Handler; import android.os.Parcel; import android.os.Parcelable; +import android.os.SystemProperties; import android.text.TextUtils; import java.util.List; @@ -46,6 +48,26 @@ public class ActivityManager { } /** + * Return the approximate per-application memory class of the current + * device. This gives you an idea of how hard a memory limit you should + * impose on your application to let the overall system work best. The + * returned value is in megabytes; the baseline Android memory class is + * 16 (which happens to be the Java heap limit of those devices); some + * device with more memory may return 24 or even higher numbers. + */ + public int getMemoryClass() { + return staticGetMemoryClass(); + } + + /** @hide */ + static public int staticGetMemoryClass() { + // Really brain dead right now -- just take this from the configured + // vm heap size, and assume it is in megabytes and thus ends with "m". + String vmHeapSize = SystemProperties.get("dalvik.vm.heapsize", "16m"); + return Integer.parseInt(vmHeapSize.substring(0, vmHeapSize.length()-1)); + } + + /** * Information you can retrieve about tasks that the user has most recently * started or visited. */ @@ -289,6 +311,11 @@ public class ActivityManager { public int pid; /** + * The UID that owns this service. + */ + public int uid; + + /** * The name of the process this service runs in. */ public String process; @@ -299,7 +326,7 @@ public class ActivityManager { public boolean foreground; /** - * The time when the service was first made activity, either by someone + * The time when the service was first made active, either by someone * starting or binding to it. */ public long activeSince; @@ -332,6 +359,48 @@ public class ActivityManager { */ public long restarting; + /** + * Bit for {@link #flags}: set if this service has been + * explicitly started. + */ + public static final int FLAG_STARTED = 1<<0; + + /** + * Bit for {@link #flags}: set if the service has asked to + * run as a foreground process. + */ + public static final int FLAG_FOREGROUND = 1<<1; + + /** + * Bit for {@link #flags): set if the service is running in a + * core system process. + */ + public static final int FLAG_SYSTEM_PROCESS = 1<<2; + + /** + * Bit for {@link #flags): set if the service is running in a + * persistent process. + */ + public static final int FLAG_PERSISTENT_PROCESS = 1<<3; + + /** + * Running flags. + */ + public int flags; + + /** + * For special services that are bound to by system code, this is + * the package that holds the binding. + */ + public String clientPackage; + + /** + * For special services that are bound to by system code, this is + * a string resource providing a user-visible label for who the + * client is. + */ + public int clientLabel; + public RunningServiceInfo() { } @@ -342,6 +411,7 @@ public class ActivityManager { public void writeToParcel(Parcel dest, int flags) { ComponentName.writeToParcel(service, dest); dest.writeInt(pid); + dest.writeInt(uid); dest.writeString(process); dest.writeInt(foreground ? 1 : 0); dest.writeLong(activeSince); @@ -350,11 +420,15 @@ public class ActivityManager { dest.writeInt(crashCount); dest.writeLong(lastActivityTime); dest.writeLong(restarting); + dest.writeInt(this.flags); + dest.writeString(clientPackage); + dest.writeInt(clientLabel); } public void readFromParcel(Parcel source) { service = ComponentName.readFromParcel(source); pid = source.readInt(); + uid = source.readInt(); process = source.readString(); foreground = source.readInt() != 0; activeSince = source.readLong(); @@ -363,6 +437,9 @@ public class ActivityManager { crashCount = source.readInt(); lastActivityTime = source.readLong(); restarting = source.readLong(); + flags = source.readInt(); + clientPackage = source.readString(); + clientLabel = source.readInt(); } public static final Creator<RunningServiceInfo> CREATOR = new Creator<RunningServiceInfo>() { @@ -401,6 +478,22 @@ public class ActivityManager { } /** + * Returns a PendingIntent you can start to show a control panel for the + * given running service. If the service does not have a control panel, + * null is returned. + */ + public PendingIntent getRunningServiceControlPanel(ComponentName service) + throws SecurityException { + try { + return ActivityManagerNative.getDefault() + .getRunningServiceControlPanel(service); + } catch (RemoteException e) { + // System dead, we will be dead too soon! + return null; + } + } + + /** * Information you can retrieve about the available memory through * {@link ActivityManager#getMemoryInfo}. */ @@ -613,6 +706,11 @@ public class ActivityManager { */ public int pid; + /** + * The user id of this process. + */ + public int uid; + public String pkgList[]; /** @@ -666,8 +764,51 @@ public class ActivityManager { */ public int lru; + /** + * Constant for {@link #importanceReasonCode}: nothing special has + * been specified for the reason for this level. + */ + public static final int REASON_UNKNOWN = 0; + + /** + * Constant for {@link #importanceReasonCode}: one of the application's + * content providers is being used by another process. The pid of + * the client process is in {@link #importanceReasonPid} and the + * target provider in this process is in + * {@link #importanceReasonComponent}. + */ + public static final int REASON_PROVIDER_IN_USE = 1; + + /** + * Constant for {@link #importanceReasonCode}: one of the application's + * content providers is being used by another process. The pid of + * the client process is in {@link #importanceReasonPid} and the + * target provider in this process is in + * {@link #importanceReasonComponent}. + */ + public static final int REASON_SERVICE_IN_USE = 2; + + /** + * The reason for {@link #importance}, if any. + */ + public int importanceReasonCode; + + /** + * For the specified values of {@link #importanceReasonCode}, this + * is the process ID of the other process that is a client of this + * process. This will be 0 if no other process is using this one. + */ + public int importanceReasonPid; + + /** + * For the specified values of {@link #importanceReasonCode}, this + * is the name of the component that is being used in this process. + */ + public ComponentName importanceReasonComponent; + public RunningAppProcessInfo() { importance = IMPORTANCE_FOREGROUND; + importanceReasonCode = REASON_UNKNOWN; } public RunningAppProcessInfo(String pProcessName, int pPid, String pArr[]) { @@ -683,17 +824,25 @@ public class ActivityManager { public void writeToParcel(Parcel dest, int flags) { dest.writeString(processName); dest.writeInt(pid); + dest.writeInt(uid); dest.writeStringArray(pkgList); dest.writeInt(importance); dest.writeInt(lru); + dest.writeInt(importanceReasonCode); + dest.writeInt(importanceReasonPid); + ComponentName.writeToParcel(importanceReasonComponent, dest); } public void readFromParcel(Parcel source) { processName = source.readString(); pid = source.readInt(); + uid = source.readInt(); pkgList = source.readStringArray(); importance = source.readInt(); lru = source.readInt(); + importanceReasonCode = source.readInt(); + importanceReasonPid = source.readInt(); + importanceReasonComponent = ComponentName.readFromParcel(source); } public static final Creator<RunningAppProcessInfo> CREATOR = @@ -727,6 +876,22 @@ public class ActivityManager { } /** + * Return information about the memory usage of one or more processes. + * + * @param pids The pids of the processes whose memory usage is to be + * retrieved. + * @return Returns an array of memory information, one for each + * requested pid. + */ + public Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids) { + try { + return ActivityManagerNative.getDefault().getProcessMemoryInfo(pids); + } catch (RemoteException e) { + return null; + } + } + + /** * Have the system perform a force stop of everything associated with * the given application package. All processes that share its uid * will be killed, all services it has running stopped, all activities diff --git a/core/java/android/app/ActivityManagerNative.java b/core/java/android/app/ActivityManagerNative.java index 447512a..3b8aee9 100644 --- a/core/java/android/app/ActivityManagerNative.java +++ b/core/java/android/app/ActivityManagerNative.java @@ -21,6 +21,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IIntentSender; import android.content.IIntentReceiver; +import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.IPackageDataObserver; @@ -29,6 +30,7 @@ import android.graphics.Bitmap; import android.net.Uri; import android.os.Binder; import android.os.Bundle; +import android.os.Debug; import android.os.Parcelable; import android.os.ParcelFileDescriptor; import android.os.RemoteException; @@ -39,9 +41,6 @@ import android.text.TextUtils; import android.util.Config; import android.util.Log; -import java.io.FileNotFoundException; -import java.io.FileDescriptor; -import java.io.IOException; import java.util.ArrayList; import java.util.List; @@ -146,6 +145,30 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeInt(result); return true; } + + case START_ACTIVITY_INTENT_SENDER_TRANSACTION: + { + data.enforceInterface(IActivityManager.descriptor); + IBinder b = data.readStrongBinder(); + IApplicationThread app = ApplicationThreadNative.asInterface(b); + IntentSender intent = IntentSender.CREATOR.createFromParcel(data); + Intent fillInIntent = null; + if (data.readInt() != 0) { + fillInIntent = Intent.CREATOR.createFromParcel(data); + } + String resolvedType = data.readString(); + IBinder resultTo = data.readStrongBinder(); + String resultWho = data.readString(); + int requestCode = data.readInt(); + int flagsMask = data.readInt(); + int flagsValues = data.readInt(); + int result = startActivityIntentSender(app, intent, + fillInIntent, resolvedType, resultTo, resultWho, + requestCode, flagsMask, flagsValues); + reply.writeNoException(); + reply.writeInt(result); + return true; + } case START_NEXT_MATCHING_ACTIVITY_TRANSACTION: { @@ -292,8 +315,12 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case ACTIVITY_IDLE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); + Configuration config = null; + if (data.readInt() != 0) { + config = Configuration.CREATOR.createFromParcel(data); + } if (token != null) { - activityIdle(token); + activityIdle(token, config); } reply.writeNoException(); return true; @@ -512,6 +539,15 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } + case GET_RUNNING_SERVICE_CONTROL_PANEL_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + ComponentName comp = ComponentName.CREATOR.createFromParcel(data); + PendingIntent pi = getRunningServiceControlPanel(comp); + reply.writeNoException(); + PendingIntent.writePendingIntentOrNullToParcel(pi, reply); + return true; + } + case START_SERVICE_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder b = data.readStrongBinder(); @@ -551,8 +587,13 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM data.enforceInterface(IActivityManager.descriptor); ComponentName className = ComponentName.readFromParcel(data); IBinder token = data.readStrongBinder(); - boolean isForeground = data.readInt() != 0; - setServiceForeground(className, token, isForeground); + int id = data.readInt(); + Notification notification = null; + if (data.readInt() != 0) { + notification = Notification.CREATOR.createFromParcel(data); + } + boolean removeNotification = data.readInt() != 0; + setServiceForeground(className, token, id, notification, removeNotification); reply.writeNoException(); return true; } @@ -606,7 +647,10 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM case SERVICE_DONE_EXECUTING_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder token = data.readStrongBinder(); - serviceDoneExecuting(token); + int type = data.readInt(); + int startId = data.readInt(); + int res = data.readInt(); + serviceDoneExecuting(token, type, startId, res); reply.writeNoException(); return true; } @@ -935,13 +979,6 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM return true; } - case SYSTEM_READY_TRANSACTION: { - data.enforceInterface(IActivityManager.descriptor); - systemReady(); - reply.writeNoException(); - return true; - } - case HANDLE_APPLICATION_ERROR_TRANSACTION: { data.enforceInterface(IActivityManager.descriptor); IBinder app = data.readStrongBinder(); @@ -1102,6 +1139,34 @@ public abstract class ActivityManagerNative extends Binder implements IActivityM reply.writeNoException(); return true; } + + case GET_PROCESS_MEMORY_INFO_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + int[] pids = data.createIntArray(); + Debug.MemoryInfo[] res = getProcessMemoryInfo(pids); + reply.writeNoException(); + reply.writeTypedArray(res, Parcelable.PARCELABLE_WRITE_RETURN_VALUE); + return true; + } + + case KILL_APPLICATION_PROCESS_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + String processName = data.readString(); + int uid = data.readInt(); + killApplicationProcess(processName, uid); + reply.writeNoException(); + return true; + } + + case OVERRIDE_PENDING_TRANSITION_TRANSACTION: { + data.enforceInterface(IActivityManager.descriptor); + IBinder token = data.readStrongBinder(); + String packageName = data.readString(); + int enterAnim = data.readInt(); + int exitAnim = data.readInt(); + overridePendingTransition(token, packageName, enterAnim, exitAnim); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -1152,6 +1217,34 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); return result; } + public int startActivityIntentSender(IApplicationThread caller, + IntentSender intent, Intent fillInIntent, String resolvedType, + IBinder resultTo, String resultWho, int requestCode, + int flagsMask, int flagsValues) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(caller != null ? caller.asBinder() : null); + intent.writeToParcel(data, 0); + if (fillInIntent != null) { + data.writeInt(1); + fillInIntent.writeToParcel(data, 0); + } else { + data.writeInt(0); + } + data.writeString(resolvedType); + data.writeStrongBinder(resultTo); + data.writeString(resultWho); + data.writeInt(requestCode); + data.writeInt(flagsMask); + data.writeInt(flagsValues); + mRemote.transact(START_ACTIVITY_INTENT_SENDER_TRANSACTION, data, reply, 0); + reply.readException(); + int result = reply.readInt(); + reply.recycle(); + data.recycle(); + return result; + } public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent) throws RemoteException { Parcel data = Parcel.obtain(); @@ -1308,12 +1401,18 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } - public void activityIdle(IBinder token) throws RemoteException + public void activityIdle(IBinder token, Configuration config) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(token); + if (config != null) { + data.writeInt(1); + config.writeToParcel(data, 0); + } else { + data.writeInt(0); + } mRemote.transact(ACTIVITY_IDLE_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); reply.readException(); data.recycle(); @@ -1616,6 +1715,21 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } + public PendingIntent getRunningServiceControlPanel(ComponentName service) + throws RemoteException + { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + service.writeToParcel(data, 0); + mRemote.transact(GET_RUNNING_SERVICE_CONTROL_PANEL_TRANSACTION, data, reply, 0); + reply.readException(); + PendingIntent res = PendingIntent.readPendingIntentOrNullFromParcel(reply); + data.recycle(); + reply.recycle(); + return res; + } + public ComponentName startService(IApplicationThread caller, Intent service, String resolvedType) throws RemoteException { @@ -1664,13 +1778,20 @@ class ActivityManagerProxy implements IActivityManager return res; } public void setServiceForeground(ComponentName className, IBinder token, - boolean isForeground) throws RemoteException { + int id, Notification notification, boolean removeNotification) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); ComponentName.writeToParcel(className, data); data.writeStrongBinder(token); - data.writeInt(isForeground ? 1 : 0); + data.writeInt(id); + if (notification != null) { + data.writeInt(1); + notification.writeToParcel(data, 0); + } else { + data.writeInt(0); + } + data.writeInt(removeNotification ? 1 : 0); mRemote.transact(SET_SERVICE_FOREGROUND_TRANSACTION, data, reply, 0); reply.readException(); data.recycle(); @@ -1737,11 +1858,15 @@ class ActivityManagerProxy implements IActivityManager reply.recycle(); } - public void serviceDoneExecuting(IBinder token) throws RemoteException { + public void serviceDoneExecuting(IBinder token, int type, int startId, + int res) throws RemoteException { Parcel data = Parcel.obtain(); Parcel reply = Parcel.obtain(); data.writeInterfaceToken(IActivityManager.descriptor); data.writeStrongBinder(token); + data.writeInt(type); + data.writeInt(startId); + data.writeInt(res); mRemote.transact(SERVICE_DONE_EXECUTING_TRANSACTION, data, reply, IBinder.FLAG_ONEWAY); reply.readException(); data.recycle(); @@ -2212,16 +2337,6 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } - public void systemReady() throws RemoteException - { - Parcel data = Parcel.obtain(); - Parcel reply = Parcel.obtain(); - data.writeInterfaceToken(IActivityManager.descriptor); - mRemote.transact(SYSTEM_READY_TRANSACTION, data, reply, 0); - reply.readException(); - data.recycle(); - reply.recycle(); - } public boolean testIsSystemReady() { /* this base class version is never called */ @@ -2408,6 +2523,47 @@ class ActivityManagerProxy implements IActivityManager data.recycle(); reply.recycle(); } + + public Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids) + throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeIntArray(pids); + mRemote.transact(GET_PROCESS_MEMORY_INFO_TRANSACTION, data, reply, 0); + reply.readException(); + Debug.MemoryInfo[] res = reply.createTypedArray(Debug.MemoryInfo.CREATOR); + data.recycle(); + reply.recycle(); + return res; + } + + public void killApplicationProcess(String processName, int uid) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeString(processName); + data.writeInt(uid); + mRemote.transact(KILL_APPLICATION_PROCESS_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + public void overridePendingTransition(IBinder token, String packageName, + int enterAnim, int exitAnim) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IActivityManager.descriptor); + data.writeStrongBinder(token); + data.writeString(packageName); + data.writeInt(enterAnim); + data.writeInt(exitAnim); + mRemote.transact(OVERRIDE_PENDING_TRANSITION_TRANSACTION, data, reply, 0); + reply.readException(); + data.recycle(); + reply.recycle(); + } + private IBinder mRemote; } diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index e045105..b116bf8 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -68,6 +68,7 @@ import android.view.WindowManagerImpl; import com.android.internal.os.BinderInternal; import com.android.internal.os.RuntimeInit; +import com.android.internal.os.SamplingProfilerIntegration; import com.android.internal.util.ArrayUtils; import org.apache.harmony.xnet.provider.jsse.OpenSSLSocketImpl; @@ -87,6 +88,8 @@ import java.util.Map; import java.util.TimeZone; import java.util.regex.Pattern; +import dalvik.system.SamplingProfiler; + final class IntentReceiverLeaked extends AndroidRuntimeException { public IntentReceiverLeaked(String msg) { super(msg); @@ -119,14 +122,15 @@ public final class ActivityThread { private static final boolean localLOGV = DEBUG ? Config.LOGD : Config.LOGV; private static final boolean DEBUG_BROADCAST = false; private static final boolean DEBUG_RESULTS = false; - private static final boolean DEBUG_BACKUP = true; + private static final boolean DEBUG_BACKUP = false; + private static final boolean DEBUG_CONFIGURATION = false; private static final long MIN_TIME_BETWEEN_GCS = 5*1000; private static final Pattern PATTERN_SEMICOLON = Pattern.compile(";"); private static final int SQLITE_MEM_RELEASED_EVENT_LOG_TAG = 75003; private static final int LOG_ON_PAUSE_CALLED = 30021; private static final int LOG_ON_RESUME_CALLED = 30022; - + public static final ActivityThread currentActivityThread() { return (ActivityThread)sThreadLocal.get(); } @@ -289,9 +293,9 @@ public final class ActivityThread { } public PackageInfo(ActivityThread activityThread, String name, - Context systemContext) { + Context systemContext, ApplicationInfo info) { mActivityThread = activityThread; - mApplicationInfo = new ApplicationInfo(); + mApplicationInfo = info != null ? info : new ApplicationInfo(); mApplicationInfo.packageName = name; mPackageName = name; mAppDir = null; @@ -314,7 +318,7 @@ public final class ActivityThread { public ApplicationInfo getApplicationInfo() { return mApplicationInfo; } - + public boolean isSecurityViolation() { return mSecurityViolation; } @@ -322,7 +326,7 @@ public final class ActivityThread { /** * Gets the array of shared libraries that are listed as * used by the given package. - * + * * @param packageName the name of the package (note: not its * file name) * @return null-ok; the array of shared libraries, each one @@ -350,7 +354,7 @@ public final class ActivityThread { * result is a single string with the names of the libraries * separated by colons, or <code>null</code> if both lists * were <code>null</code> or empty. - * + * * @param list1 null-ok; the first list * @param list2 null-ok; the second list * @return null-ok; the combination @@ -378,7 +382,7 @@ public final class ActivityThread { if (dupCheck && ArrayUtils.contains(list1, s)) { continue; } - + if (first) { first = false; } else { @@ -390,7 +394,7 @@ public final class ActivityThread { return result.toString(); } - + public ClassLoader getClassLoader() { synchronized (this) { if (mClassLoader != null) { @@ -428,7 +432,7 @@ public final class ActivityThread { if ((mSharedLibraries != null) || (instrumentationLibs != null)) { - zip = + zip = combineLibs(mSharedLibraries, instrumentationLibs) + ':' + zip; } @@ -481,13 +485,14 @@ public final class ActivityThread { return mResources; } - public Application makeApplication(boolean forceDefaultAppClass) { + public Application makeApplication(boolean forceDefaultAppClass, + Instrumentation instrumentation) { if (mApplication != null) { return mApplication; } - + Application app = null; - + String appClass = mApplicationInfo.className; if (forceDefaultAppClass || (appClass == null)) { appClass = "android.app.Application"; @@ -508,9 +513,23 @@ public final class ActivityThread { } } mActivityThread.mAllApplications.add(app); - return mApplication = app; + mApplication = app; + + if (instrumentation != null) { + try { + instrumentation.callApplicationOnCreate(app); + } catch (Exception e) { + if (!instrumentation.onException(app, e)) { + throw new RuntimeException( + "Unable to create application " + app.getClass().getName() + + ": " + e.toString(), e); + } + } + } + + return app; } - + public void removeContextRegistrations(Context context, String who, String what) { HashMap<BroadcastReceiver, ReceiverDispatcher> rmap = @@ -643,13 +662,13 @@ public final class ActivityThread { final static class InnerReceiver extends IIntentReceiver.Stub { final WeakReference<ReceiverDispatcher> mDispatcher; final ReceiverDispatcher mStrongRef; - + InnerReceiver(ReceiverDispatcher rd, boolean strong) { mDispatcher = new WeakReference<ReceiverDispatcher>(rd); mStrongRef = strong ? rd : null; } public void performReceive(Intent intent, int resultCode, - String data, Bundle extras, boolean ordered) { + String data, Bundle extras, boolean ordered, boolean sticky) { ReceiverDispatcher rd = mDispatcher.get(); if (DEBUG_BROADCAST) { int seq = intent.getIntExtra("seq", -1); @@ -657,11 +676,12 @@ public final class ActivityThread { + " to " + rd); } if (rd != null) { - rd.performReceive(intent, resultCode, data, extras, ordered); + rd.performReceive(intent, resultCode, data, extras, + ordered, sticky); } } } - + final IIntentReceiver.Stub mIIntentReceiver; final BroadcastReceiver mReceiver; final Context mContext; @@ -677,6 +697,7 @@ public final class ActivityThread { private String mCurData; private Bundle mCurMap; private boolean mCurOrdered; + private boolean mCurSticky; public void run() { BroadcastReceiver receiver = mReceiver; @@ -702,6 +723,7 @@ public final class ActivityThread { receiver.setResult(mCurCode, mCurData, mCurMap); receiver.clearAbortBroadcast(); receiver.setOrderedHint(mCurOrdered); + receiver.setInitialStickyHint(mCurSticky); receiver.onReceive(mContext, intent); } catch (Exception e) { if (mRegistered && mCurOrdered) { @@ -770,7 +792,7 @@ public final class ActivityThread { BroadcastReceiver getIntentReceiver() { return mReceiver; } - + IIntentReceiver getIIntentReceiver() { return mIIntentReceiver; } @@ -784,7 +806,7 @@ public final class ActivityThread { } public void performReceive(Intent intent, int resultCode, - String data, Bundle extras, boolean ordered) { + String data, Bundle extras, boolean ordered, boolean sticky) { if (DEBUG_BROADCAST) { int seq = intent.getIntExtra("seq", -1); Log.i(TAG, "Enqueueing broadcast " + intent.getAction() + " seq=" + seq @@ -796,6 +818,7 @@ public final class ActivityThread { args.mCurData = data; args.mCurMap = extras; args.mCurOrdered = ordered; + args.mCurSticky = sticky; if (!mActivityThread.post(args)) { if (mRegistered) { IActivityManager mgr = ActivityManagerNative.getDefault(); @@ -901,7 +924,7 @@ public final class ActivityThread { private static class InnerConnection extends IServiceConnection.Stub { final WeakReference<ServiceDispatcher> mDispatcher; - + InnerConnection(ServiceDispatcher sd) { mDispatcher = new WeakReference<ServiceDispatcher>(sd); } @@ -913,7 +936,7 @@ public final class ActivityThread { } } } - + private final HashMap<ComponentName, ConnectionInfo> mActiveConnections = new HashMap<ComponentName, ConnectionInfo>(); @@ -965,7 +988,7 @@ public final class ActivityThread { IServiceConnection getIServiceConnection() { return mIServiceConnection; } - + int getFlags() { return mFlags; } @@ -1112,6 +1135,7 @@ public final class ActivityThread { boolean stopped; boolean hideForNow; Configuration newConfig; + Configuration createdConfig; ActivityRecord nextIdle; ActivityInfo activityInfo; @@ -1191,7 +1215,7 @@ public final class ActivityThread { + " mode=" + backupMode + "}"; } } - + private static final class CreateServiceData { IBinder token; ServiceInfo info; @@ -1215,6 +1239,7 @@ public final class ActivityThread { private static final class ServiceArgsData { IBinder token; int startId; + int flags; Intent args; public String toString() { return "ServiceArgsData{token=" + token + " startId=" + startId @@ -1270,10 +1295,10 @@ public final class ActivityThread { private static final String HEAP_COLUMN = "%17s %8s %8s %8s %8s"; private static final String ONE_COUNT_COLUMN = "%17s %8d"; private static final String TWO_COUNT_COLUMNS = "%17s %8d %17s %8d"; - + // Formatting for checkin service - update version if row format changes private static final int ACTIVITY_THREAD_CHECKIN_VERSION = 1; - + public final void schedulePauseActivity(IBinder token, boolean finished, boolean userLeaving, int configChanges) { queueOrSendMessage( @@ -1342,7 +1367,7 @@ public final class ActivityThread { synchronized (mRelaunchingActivities) { mRelaunchingActivities.add(r); } - + queueOrSendMessage(H.RELAUNCH_ACTIVITY, r, configChanges); } @@ -1417,10 +1442,11 @@ public final class ActivityThread { } public final void scheduleServiceArgs(IBinder token, int startId, - Intent args) { + int flags ,Intent args) { ServiceArgsData s = new ServiceArgsData(); s.token = token; s.startId = startId; + s.flags = flags; s.args = args; queueOrSendMessage(H.SERVICE_ARGS, s); @@ -1436,7 +1462,6 @@ public final class ActivityThread { Bundle instrumentationArgs, IInstrumentationWatcher instrumentationWatcher, int debugMode, boolean isRestrictedBackupMode, Configuration config, Map<String, IBinder> services) { - Process.setArgV0(processName); if (services != null) { // Setup the service cache in the ServiceManager @@ -1461,6 +1486,10 @@ public final class ActivityThread { queueOrSendMessage(H.EXIT_APPLICATION, null); } + public final void scheduleSuicide() { + queueOrSendMessage(H.SUICIDE, null); + } + public void requestThumbnail(IBinder token) { queueOrSendMessage(H.REQUEST_THUMBNAIL, token); } @@ -1504,11 +1533,11 @@ public final class ActivityThread { // correctly ordered, since these are one-way calls and the binder driver // applies transaction ordering per object for such calls. public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, - int resultCode, String dataStr, Bundle extras, boolean ordered) - throws RemoteException { - receiver.performReceive(intent, resultCode, dataStr, extras, ordered); + int resultCode, String dataStr, Bundle extras, boolean ordered, + boolean sticky) throws RemoteException { + receiver.performReceive(intent, resultCode, dataStr, extras, ordered, sticky); } - + public void scheduleLowMemory() { queueOrSendMessage(H.LOW_MEMORY, null); } @@ -1524,7 +1553,7 @@ public final class ActivityThread { } catch (RemoteException e) { } } - + public void profilerControl(boolean start, String path, ParcelFileDescriptor fd) { ProfilerControlData pcd = new ProfilerControlData(); pcd.path = path; @@ -1543,7 +1572,11 @@ public final class ActivityThread { Log.w(TAG, "Failed setting process group to " + group, e); } } - + + public void getMemoryInfo(Debug.MemoryInfo outInfo) { + Debug.getMemoryInfo(outInfo); + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { long nativeMax = Debug.getNativeHeapSize() / 1024; @@ -1579,7 +1612,7 @@ public final class ActivityThread { long sqliteAllocated = SQLiteDebug.getHeapAllocatedSize() / 1024; SQLiteDebug.PagerStats stats = new SQLiteDebug.PagerStats(); SQLiteDebug.getPagerStats(stats); - + // Check to see if we were called by checkin server. If so, print terse format. boolean doCheckinFormat = false; if (args != null) { @@ -1587,79 +1620,79 @@ public final class ActivityThread { if ("-c".equals(arg)) doCheckinFormat = true; } } - + // For checkin, we print one long comma-separated list of values if (doCheckinFormat) { // NOTE: if you change anything significant below, also consider changing // ACTIVITY_THREAD_CHECKIN_VERSION. - String processName = (mBoundApplication != null) + String processName = (mBoundApplication != null) ? mBoundApplication.processName : "unknown"; - + // Header pw.print(ACTIVITY_THREAD_CHECKIN_VERSION); pw.print(','); pw.print(Process.myPid()); pw.print(','); pw.print(processName); pw.print(','); - + // Heap info - max pw.print(nativeMax); pw.print(','); pw.print(dalvikMax); pw.print(','); pw.print("N/A,"); pw.print(nativeMax + dalvikMax); pw.print(','); - + // Heap info - allocated pw.print(nativeAllocated); pw.print(','); pw.print(dalvikAllocated); pw.print(','); pw.print("N/A,"); pw.print(nativeAllocated + dalvikAllocated); pw.print(','); - + // Heap info - free pw.print(nativeFree); pw.print(','); pw.print(dalvikFree); pw.print(','); pw.print("N/A,"); pw.print(nativeFree + dalvikFree); pw.print(','); - + // Heap info - proportional set size pw.print(memInfo.nativePss); pw.print(','); pw.print(memInfo.dalvikPss); pw.print(','); pw.print(memInfo.otherPss); pw.print(','); pw.print(memInfo.nativePss + memInfo.dalvikPss + memInfo.otherPss); pw.print(','); - + // Heap info - shared - pw.print(nativeShared); pw.print(','); - pw.print(dalvikShared); pw.print(','); - pw.print(otherShared); pw.print(','); + pw.print(nativeShared); pw.print(','); + pw.print(dalvikShared); pw.print(','); + pw.print(otherShared); pw.print(','); pw.print(nativeShared + dalvikShared + otherShared); pw.print(','); - + // Heap info - private - pw.print(nativePrivate); pw.print(','); + pw.print(nativePrivate); pw.print(','); pw.print(dalvikPrivate); pw.print(','); pw.print(otherPrivate); pw.print(','); pw.print(nativePrivate + dalvikPrivate + otherPrivate); pw.print(','); - + // Object counts pw.print(viewInstanceCount); pw.print(','); pw.print(viewRootInstanceCount); pw.print(','); pw.print(appContextInstanceCount); pw.print(','); pw.print(activityInstanceCount); pw.print(','); - + pw.print(globalAssetCount); pw.print(','); pw.print(globalAssetManagerCount); pw.print(','); pw.print(binderLocalObjectCount); pw.print(','); pw.print(binderProxyObjectCount); pw.print(','); - + pw.print(binderDeathObjectCount); pw.print(','); pw.print(openSslSocketCount); pw.print(','); - + // SQL pw.print(sqliteAllocated); pw.print(','); - pw.print(stats.databaseBytes / 1024); pw.print(','); + pw.print(stats.databaseBytes / 1024); pw.print(','); pw.print(stats.numPagers); pw.print(','); pw.print((stats.totalBytes - stats.referencedBytes) / 1024); pw.print(','); pw.print(stats.referencedBytes / 1024); pw.print('\n'); - + return; } - + // otherwise, show human-readable format printRow(pw, HEAP_COLUMN, "", "native", "dalvik", "other", "total"); printRow(pw, HEAP_COLUMN, "size:", nativeMax, dalvikMax, "N/A", nativeMax + dalvikMax); @@ -1692,7 +1725,7 @@ public final class ActivityThread { printRow(pw, ONE_COUNT_COLUMN, "Death Recipients:", binderDeathObjectCount); printRow(pw, ONE_COUNT_COLUMN, "OpenSSL Sockets:", openSslSocketCount); - + // SQLite mem info pw.println(" "); pw.println(" SQL"); @@ -1701,7 +1734,7 @@ public final class ActivityThread { printRow(pw, TWO_COUNT_COLUMNS, "numPagers:", stats.numPagers, "inactivePageKB:", (stats.totalBytes - stats.referencedBytes) / 1024); printRow(pw, ONE_COUNT_COLUMN, "activePageKB:", stats.referencedBytes / 1024); - + // Asset details. String assetAlloc = AssetManager.getAssetAllocations(); if (assetAlloc != null) { @@ -1717,6 +1750,10 @@ public final class ActivityThread { } private final class H extends Handler { + private H() { + SamplingProfiler.getInstance().setEventThread(mLooper.getThread()); + } + public static final int LAUNCH_ACTIVITY = 100; public static final int PAUSE_ACTIVITY = 101; public static final int PAUSE_ACTIVITY_FINISHING= 102; @@ -1746,7 +1783,9 @@ public final class ActivityThread { public static final int RELAUNCH_ACTIVITY = 126; public static final int PROFILER_CONTROL = 127; public static final int CREATE_BACKUP_AGENT = 128; - public static final int DESTROY_BACKUP_AGENT = 129; + public static final int DESTROY_BACKUP_AGENT = 129; + public static final int SUICIDE = 130; + public static final int REMOVE_PROVIDER = 131; String codeToString(int code) { if (localLOGV) { switch (code) { @@ -1780,6 +1819,8 @@ public final class ActivityThread { case PROFILER_CONTROL: return "PROFILER_CONTROL"; case CREATE_BACKUP_AGENT: return "CREATE_BACKUP_AGENT"; case DESTROY_BACKUP_AGENT: return "DESTROY_BACKUP_AGENT"; + case SUICIDE: return "SUICIDE"; + case REMOVE_PROVIDER: return "REMOVE_PROVIDER"; } } return "(unknown)"; @@ -1799,6 +1840,7 @@ public final class ActivityThread { } break; case PAUSE_ACTIVITY: handlePauseActivity((IBinder)msg.obj, false, msg.arg1 != 0, msg.arg2); + maybeSnapshot(); break; case PAUSE_ACTIVITY_FINISHING: handlePauseActivity((IBinder)msg.obj, true, msg.arg1 != 0, msg.arg2); @@ -1841,6 +1883,7 @@ public final class ActivityThread { break; case RECEIVER: handleReceiver((ReceiverData)msg.obj); + maybeSnapshot(); break; case CREATE_SERVICE: handleCreateService((CreateServiceData)msg.obj); @@ -1856,6 +1899,7 @@ public final class ActivityThread { break; case STOP_SERVICE: handleStopService((IBinder)msg.obj); + maybeSnapshot(); break; case REQUEST_THUMBNAIL: handleRequestThumbnail((IBinder)msg.obj); @@ -1888,6 +1932,19 @@ public final class ActivityThread { case DESTROY_BACKUP_AGENT: handleDestroyBackupAgent((CreateBackupAgentData)msg.obj); break; + case SUICIDE: + Process.killProcess(Process.myPid()); + break; + case REMOVE_PROVIDER: + completeRemoveProvider((IContentProvider)msg.obj); + break; + } + } + + void maybeSnapshot() { + if (mBoundApplication != null) { + SamplingProfilerIntegration.writeSnapshot( + mBoundApplication.processName); } } } @@ -1906,7 +1963,8 @@ public final class ActivityThread { (a.activity != null ? a.activity.mFinished : false)); if (a.activity != null && !a.activity.mFinished) { try { - am.activityIdle(a.token); + am.activityIdle(a.token, a.createdConfig); + a.createdConfig = null; } catch (RemoteException ex) { } } @@ -1930,13 +1988,13 @@ public final class ActivityThread { 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; @@ -1987,7 +2045,7 @@ public final class ActivityThread { final ArrayList<ActivityRecord> mRelaunchingActivities = new ArrayList<ActivityRecord>(); Configuration mPendingConfiguration = null; - + // These can be accessed by multiple threads; mPackages is the lock. // XXX For now we keep around information about all packages we have // seen, not removing entries from this map. @@ -2122,7 +2180,7 @@ public final class ActivityThread { return false; } } - + ActivityThread() { } @@ -2155,17 +2213,17 @@ public final class ActivityThread { public Application getApplication() { return mInitialApplication; } - + public String getProcessName() { return mBoundApplication.processName; } - + public ApplicationContext getSystemContext() { synchronized (this) { if (mSystemContext == null) { ApplicationContext context = ApplicationContext.createSystemContext(this); - PackageInfo info = new PackageInfo(this, "android", context); + PackageInfo info = new PackageInfo(this, "android", context, null); context.init(info, null, this); context.getResources().updateConfiguration( getConfiguration(), getDisplayMetricsLocked(false)); @@ -2177,6 +2235,13 @@ public final class ActivityThread { return mSystemContext; } + public void installSystemApplicationInfo(ApplicationInfo info) { + synchronized (this) { + ApplicationContext context = getSystemContext(); + context.init(new PackageInfo(this, "android", context, info), null, this); + } + } + void scheduleGcIdler() { if (!mGcIdlerScheduled) { mGcIdlerScheduled = true; @@ -2214,7 +2279,7 @@ public final class ActivityThread { } return aInfo; } - + public final Activity startActivityNow(Activity parent, String id, Intent intent, ActivityInfo activityInfo, IBinder token, Bundle state, Object lastNonConfigurationInstance) { @@ -2297,7 +2362,7 @@ public final class ActivityThread { r.packageInfo = getPackageInfo(aInfo.applicationInfo, Context.CONTEXT_INCLUDE_CODE); } - + ComponentName component = r.intent.getComponent(); if (component == null) { component = r.intent.resolveActivity( @@ -2328,8 +2393,8 @@ public final class ActivityThread { } try { - Application app = r.packageInfo.makeApplication(false); - + Application app = r.packageInfo.makeApplication(false, mInstrumentation); + if (localLOGV) Log.v(TAG, "Performing launch of " + r); if (localLOGV) Log.v( TAG, r + ": app=" + app @@ -2344,11 +2409,13 @@ public final class ActivityThread { appContext.setOuterContext(activity); CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager()); Configuration config = new Configuration(mConfiguration); + if (DEBUG_CONFIGURATION) Log.v(TAG, "Launching activity " + + r.activityInfo.name + " with config " + 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; } @@ -2417,6 +2484,7 @@ public final class ActivityThread { Activity a = performLaunchActivity(r, customIntent); if (a != null) { + r.createdConfig = new Configuration(a.getResources().getConfiguration()); handleResumeActivity(r.token, false, r.isForward); if (!r.activity.mFinished && r.startsNotResumed) { @@ -2486,7 +2554,7 @@ public final class ActivityThread { } } } - + private final void handleNewIntent(NewIntentData data) { performNewIntents(data.token, data.intents); } @@ -2523,8 +2591,8 @@ public final class ActivityThread { } try { - Application app = packageInfo.makeApplication(false); - + Application app = packageInfo.makeApplication(false, mInstrumentation); + if (localLOGV) Log.v( TAG, "Performing receive of " + data.intent + ": app=" + app @@ -2581,7 +2649,7 @@ public final class ActivityThread { + " already exists"); return; } - + BackupAgent agent = null; String classname = data.appInfo.backupAgentName; if (classname == null) { @@ -2635,7 +2703,7 @@ public final class ActivityThread { // Tear down a BackupAgent private final void handleDestroyBackupAgent(CreateBackupAgentData data) { if (DEBUG_BACKUP) Log.v(TAG, "handleDestroyBackupAgent: " + data); - + PackageInfo packageInfo = getPackageInfoNoCheck(data.appInfo); String packageName = packageInfo.mPackageName; BackupAgent agent = mBackupAgents.get(packageName); @@ -2677,14 +2745,15 @@ public final class ActivityThread { ApplicationContext context = new ApplicationContext(); context.init(packageInfo, null, this); - Application app = packageInfo.makeApplication(false); + Application app = packageInfo.makeApplication(false, mInstrumentation); context.setOuterContext(service); service.attach(context, this, data.info.name, data.token, app, ActivityManagerNative.getDefault()); service.onCreate(); mServices.put(data.token, service); try { - ActivityManagerNative.getDefault().serviceDoneExecuting(data.token); + ActivityManagerNative.getDefault().serviceDoneExecuting( + data.token, 0, 0, 0); } catch (RemoteException e) { // nothing to do. } @@ -2710,7 +2779,7 @@ public final class ActivityThread { } else { s.onRebind(data.intent); ActivityManagerNative.getDefault().serviceDoneExecuting( - data.token); + data.token, 0, 0, 0); } } catch (RemoteException ex) { } @@ -2736,7 +2805,7 @@ public final class ActivityThread { data.token, data.intent, doRebind); } else { ActivityManagerNative.getDefault().serviceDoneExecuting( - data.token); + data.token, 0, 0, 0); } } catch (RemoteException ex) { } @@ -2773,9 +2842,10 @@ public final class ActivityThread { if (data.args != null) { data.args.setExtrasClassLoader(s.getClassLoader()); } - s.onStart(data.args, data.startId); + int res = s.onStartCommand(data.args, data.flags, data.startId); try { - ActivityManagerNative.getDefault().serviceDoneExecuting(data.token); + ActivityManagerNative.getDefault().serviceDoneExecuting( + data.token, 1, data.startId, res); } catch (RemoteException e) { // nothing to do. } @@ -2801,7 +2871,8 @@ public final class ActivityThread { ((ApplicationContext) context).scheduleFinalCleanup(who, "Service"); } try { - ActivityManagerNative.getDefault().serviceDoneExecuting(token); + ActivityManagerNative.getDefault().serviceDoneExecuting( + token, 0, 0, 0); } catch (RemoteException e) { // nothing to do. } @@ -2837,9 +2908,9 @@ public final class ActivityThread { } r.activity.performResume(); - EventLog.writeEvent(LOG_ON_RESUME_CALLED, + EventLog.writeEvent(LOG_ON_RESUME_CALLED, r.activity.getComponentName().getClassName()); - + r.paused = false; r.stopped = false; if (r.activity.mStartedActivity) { @@ -2875,7 +2946,7 @@ public final class ActivityThread { final int forwardBit = isForward ? WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0; - + // If the window hasn't yet been added to the window manager, // and this guy didn't finish itself or start another activity, // then go ahead and add the window. @@ -2904,9 +2975,11 @@ public final class ActivityThread { // The window is now visible if it has been added, we are not // simply finishing, and we are not starting another activity. - if (!r.activity.mFinished && r.activity.mDecor != null - && !r.hideForNow) { + if (!r.activity.mFinished && !a.mStartedActivity + && r.activity.mDecor != null && !r.hideForNow) { if (r.newConfig != null) { + if (DEBUG_CONFIGURATION) Log.v(TAG, "Resuming activity " + + r.activityInfo.name + " with newConfig " + r.newConfig); performConfigurationChanged(r.activity, r.newConfig); r.newConfig = null; } @@ -2919,9 +2992,11 @@ public final class ActivityThread { l.softInputMode = (l.softInputMode & (~WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION)) | forwardBit; - ViewManager wm = a.getWindowManager(); - View decor = r.window.getDecorView(); - wm.updateViewLayout(decor, l); + if (r.activity.mVisibleFromClient) { + ViewManager wm = a.getWindowManager(); + View decor = r.window.getDecorView(); + wm.updateViewLayout(decor, l); + } } r.activity.mVisibleFromServer = true; mNumVisibleActivities++; @@ -2994,7 +3069,7 @@ public final class ActivityThread { if (userLeaving) { performUserLeavingActivity(r); } - + r.activity.mConfigChangeFlags |= configChanges; Bundle state = performPauseActivity(token, finished, true); @@ -3146,6 +3221,8 @@ public final class ActivityThread { } } if (r.newConfig != null) { + if (DEBUG_CONFIGURATION) Log.v(TAG, "Updating activity vis " + + r.activityInfo.name + " with new config " + r.newConfig); performConfigurationChanged(r.activity, r.newConfig); r.newConfig = null; } @@ -3171,7 +3248,7 @@ public final class ActivityThread { + " win=" + r.window); updateVisibility(r, show); - + // Tell activity manager we have been stopped. try { ActivityManagerNative.getDefault().activityStopped( @@ -3287,7 +3364,7 @@ public final class ActivityThread { try { r.activity.mCalled = false; mInstrumentation.callActivityOnPause(r.activity); - EventLog.writeEvent(LOG_ON_PAUSE_CALLED, + EventLog.writeEvent(LOG_ON_PAUSE_CALLED, r.activity.getComponentName().getClassName()); if (!r.activity.mCalled) { throw new SuperNotCalledException( @@ -3344,7 +3421,7 @@ public final class ActivityThread { + ": " + e.toString(), e); } } - + } try { r.activity.mCalled = false; @@ -3426,6 +3503,10 @@ public final class ActivityThread { unscheduleGcIdler(); Configuration changedConfig = null; + + if (DEBUG_CONFIGURATION) Log.v(TAG, "Relaunching activity " + + tmp.token + " with configChanges=0x" + + Integer.toHexString(configChanges)); // First: make sure we have the most recent configuration and most // recent version of the activity, or skip it if some previous call @@ -3443,38 +3524,42 @@ public final class ActivityThread { N--; } } - + if (tmp == null) { + if (DEBUG_CONFIGURATION) Log.v(TAG, "Abort, activity not relaunching!"); return; } - + if (mPendingConfiguration != null) { changedConfig = mPendingConfiguration; mPendingConfiguration = null; } } + + if (DEBUG_CONFIGURATION) Log.v(TAG, "Relaunching activity " + + tmp.token + ": changedConfig=" + changedConfig); // If there was a pending configuration change, execute it first. if (changedConfig != null) { handleConfigurationChanged(changedConfig); } - + ActivityRecord r = mActivities.get(tmp.token); - if (localLOGV) Log.v(TAG, "Handling relaunch of " + r); + if (DEBUG_CONFIGURATION) Log.v(TAG, "Handling relaunch of " + r); if (r == null) { return; } - + r.activity.mConfigChangeFlags |= configChanges; Intent currentIntent = r.activity.mIntent; - + Bundle savedState = null; if (!r.paused) { savedState = performPauseActivity(r.token, false, true); } - + handleDestroyActivity(r.token, false, configChanges, true); - + r.activity = null; r.window = null; r.hideForNow = false; @@ -3498,7 +3583,7 @@ public final class ActivityThread { if (savedState != null) { r.state = savedState; } - + handleLaunchActivity(r, currentIntent); } @@ -3528,7 +3613,7 @@ public final class ActivityThread { boolean allActivities, Configuration newConfig) { ArrayList<ComponentCallbacks> callbacks = new ArrayList<ComponentCallbacks>(); - + if (mActivities.size() > 0) { Iterator<ActivityRecord> it = mActivities.values().iterator(); while (it.hasNext()) { @@ -3546,6 +3631,8 @@ public final class ActivityThread { // the activity manager may, before then, decide the // activity needs to be destroyed to handle its new // configuration. + if (DEBUG_CONFIGURATION) Log.v(TAG, "Setting activity " + + ar.activityInfo.name + " newConfig=" + newConfig); ar.newConfig = newConfig; } } @@ -3569,10 +3656,10 @@ public final class ActivityThread { for (int i=0; i<N; i++) { callbacks.add(mAllApplications.get(i)); } - + return callbacks; } - + private final void performConfigurationChanged( ComponentCallbacks cb, Configuration config) { // Only for Activity objects, check that they actually call up to their @@ -3582,18 +3669,18 @@ public final class ActivityThread { if (activity != null) { activity.mCalled = false; } - + boolean shouldChangeConfig = false; if ((activity == null) || (activity.mCurrentConfig == null)) { shouldChangeConfig = true; } else { - + // If the new config is the same as the config this Activity // is already running with then don't bother calling // onConfigurationChanged int diff = activity.mCurrentConfig.diff(config); if (diff != 0) { - + // If this activity doesn't handle any of the config changes // then don't bother calling onConfigurationChanged as we're // going to destroy it. @@ -3602,10 +3689,12 @@ public final class ActivityThread { } } } - + + if (DEBUG_CONFIGURATION) Log.v(TAG, "Config callback " + cb + + ": shouldChangeConfig=" + shouldChangeConfig); if (shouldChangeConfig) { cb.onConfigurationChanged(config); - + if (activity != null) { if (!activity.mCalled) { throw new SuperNotCalledException( @@ -3619,16 +3708,19 @@ public final class ActivityThread { } final void handleConfigurationChanged(Configuration config) { - + synchronized (mRelaunchingActivities) { if (mPendingConfiguration != null) { config = mPendingConfiguration; mPendingConfiguration = null; } } - + ArrayList<ComponentCallbacks> callbacks = new ArrayList<ComponentCallbacks>(); + + if (DEBUG_CONFIGURATION) Log.v(TAG, "Handle configuration changed: " + + config); synchronized(mPackages) { if (mConfiguration == null) { @@ -3664,10 +3756,10 @@ public final class ActivityThread { } } } - + callbacks = collectComponentCallbacksLocked(false, config); } - + final int N = callbacks.size(); for (int i=0; i<N; i++) { performConfigurationChanged(callbacks.get(i), config); @@ -3679,6 +3771,9 @@ public final class ActivityThread { if (r == null || r.activity == null) { return; } + + if (DEBUG_CONFIGURATION) Log.v(TAG, "Handle activity config changed: " + + r.activityInfo.name); performConfigurationChanged(r.activity, mConfiguration); } @@ -3702,7 +3797,7 @@ public final class ActivityThread { Debug.stopMethodTracing(); } } - + final void handleLowMemory() { ArrayList<ComponentCallbacks> callbacks = new ArrayList<ComponentCallbacks>(); @@ -3710,7 +3805,7 @@ public final class ActivityThread { synchronized(mPackages) { callbacks = collectComponentCallbacksLocked(true, null); } - + final int N = callbacks.size(); for (int i=0; i<N; i++) { callbacks.get(i).onLowMemory(); @@ -3721,7 +3816,7 @@ public final class ActivityThread { int sqliteReleased = SQLiteDatabase.releaseMemory(); EventLog.writeEvent(SQLITE_MEM_RELEASED_EVENT_LOG_TAG, sqliteReleased); } - + // Ask graphics to free up as much as possible (font/image caches) Canvas.freeCaches(); @@ -3737,6 +3832,7 @@ public final class ActivityThread { //Process.setUid(data.appInfo.uid); // send up app name; do this *before* waiting for debugger + Process.setArgV0(data.processName); android.ddm.DdmHandleAppName.setAppName(data.processName); /* @@ -3768,7 +3864,7 @@ public final class ActivityThread { == 0) { Bitmap.setDefaultDensity(DisplayMetrics.DENSITY_DEFAULT); } - + if (data.debugMode != IApplicationThread.DEBUG_OFF) { // XXX should have option to change the port. Debug.changeDebugPort(8100); @@ -3859,7 +3955,7 @@ public final class ActivityThread { // If the app is being launched for full backup or restore, bring it up in // a restricted environment with the base application class. - Application app = data.info.makeApplication(data.restrictedBackupMode); + Application app = data.info.makeApplication(data.restrictedBackupMode, null); mInitialApplication = app; List<ProviderInfo> providers = data.providers; @@ -3992,22 +4088,51 @@ public final class ActivityThread { } else { prc.count--; if(prc.count == 0) { - mProviderRefCountMap.remove(jBinder); - //invoke removeProvider to dereference provider - removeProviderLocked(provider); + // Schedule the actual remove asynchronously, since we + // don't know the context this will be called in. + // TODO: it would be nice to post a delayed message, so + // if we come back and need the same provider quickly + // we will still have it available. + Message msg = mH.obtainMessage(H.REMOVE_PROVIDER, provider); + mH.sendMessage(msg); } //end if } //end else } //end synchronized return true; } - public final void removeProviderLocked(IContentProvider provider) { + final void completeRemoveProvider(IContentProvider provider) { + IBinder jBinder = provider.asBinder(); + String name = null; + synchronized(mProviderMap) { + ProviderRefCount prc = mProviderRefCountMap.get(jBinder); + if(prc != null && prc.count == 0) { + mProviderRefCountMap.remove(jBinder); + //invoke removeProvider to dereference provider + name = removeProviderLocked(provider); + } + } + + if (name != null) { + try { + if(localLOGV) Log.v(TAG, "removeProvider::Invoking " + + "ActivityManagerNative.removeContentProvider(" + name); + ActivityManagerNative.getDefault().removeContentProvider( + getApplicationThread(), name); + } catch (RemoteException e) { + //do nothing content provider object is dead any way + } //end catch + } + } + + public final String removeProviderLocked(IContentProvider provider) { if (provider == null) { - return; + return null; } IBinder providerBinder = provider.asBinder(); - boolean amRemoveFlag = false; + String name = null; + // remove the provider from mProviderMap Iterator<ProviderRecord> iter = mProviderMap.values().iterator(); while (iter.hasNext()) { @@ -4017,7 +4142,7 @@ public final class ActivityThread { //find if its published by this process itself if(pr.mLocalProvider != null) { if(localLOGV) Log.i(TAG, "removeProvider::found local provider returning"); - return; + return name; } if(localLOGV) Log.v(TAG, "removeProvider::Not local provider Unlinking " + "death recipient"); @@ -4025,18 +4150,13 @@ public final class ActivityThread { myBinder.unlinkToDeath(pr, 0); iter.remove(); //invoke remove only once for the very first name seen - if(!amRemoveFlag) { - try { - if(localLOGV) Log.v(TAG, "removeProvider::Invoking " + - "ActivityManagerNative.removeContentProvider("+pr.mName); - ActivityManagerNative.getDefault().removeContentProvider(getApplicationThread(), pr.mName); - amRemoveFlag = true; - } catch (RemoteException e) { - //do nothing content provider object is dead any way - } //end catch + if(name == null) { + name = pr.mName; } } //end if myBinder } //end while iter + + return name; } final void removeDeadProvider(String name, IContentProvider provider) { @@ -4193,6 +4313,8 @@ public final class ActivityThread { } public static final void main(String[] args) { + SamplingProfilerIntegration.start(); + Process.setArgV0("<pre-initialized>"); Looper.prepareMainLooper(); @@ -4207,9 +4329,9 @@ public final class ActivityThread { } thread.detach(); - String name; - if (thread.mInitialApplication != null) name = thread.mInitialApplication.getPackageName(); - else name = "<unknown>"; + String name = (thread.mInitialApplication != null) + ? thread.mInitialApplication.getPackageName() + : "<unknown>"; Log.i(TAG, "Main thread of " + name + " is now exiting"); } } diff --git a/core/java/android/app/ApplicationContext.java b/core/java/android/app/ApplicationContext.java index 92929ea..f48f150 100644 --- a/core/java/android/app/ApplicationContext.java +++ b/core/java/android/app/ApplicationContext.java @@ -22,8 +22,6 @@ import com.google.android.collect.Maps; import org.xmlpull.v1.XmlPullParserException; -import android.bluetooth.BluetoothDevice; -import android.bluetooth.IBluetoothDevice; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.ContentResolver; @@ -40,6 +38,7 @@ import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; +import android.content.pm.FeatureInfo; import android.content.pm.IPackageDataObserver; import android.content.pm.IPackageDeleteObserver; import android.content.pm.IPackageInstallObserver; @@ -59,8 +58,6 @@ import android.content.res.XmlResourceParser; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase.CursorFactory; import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; import android.graphics.drawable.Drawable; import android.hardware.SensorManager; import android.location.ILocationManager; @@ -78,7 +75,6 @@ import android.os.Handler; import android.os.IBinder; import android.os.IPowerManager; import android.os.Looper; -import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -88,14 +84,14 @@ import android.os.FileUtils.FileStatus; import android.telephony.TelephonyManager; import android.text.ClipboardManager; import android.util.AndroidRuntimeException; -import android.util.DisplayMetrics; import android.util.Log; import android.view.ContextThemeWrapper; -import android.view.Display; import android.view.LayoutInflater; import android.view.WindowManagerImpl; import android.view.accessibility.AccessibilityManager; import android.view.inputmethod.InputMethodManager; +import android.accounts.AccountManager; +import android.accounts.IAccountManager; import java.io.File; import java.io.FileInputStream; @@ -160,9 +156,6 @@ class ApplicationContext extends Context { private static ConnectivityManager sConnectivityManager; private static WifiManager sWifiManager; private static LocationManager sLocationManager; - private static boolean sIsBluetoothDeviceCached = false; - private static BluetoothDevice sBluetoothDevice; - private static IWallpaperService sWallpaperService; private static final HashMap<File, SharedPreferencesImpl> sSharedPrefs = new HashMap<File, SharedPreferencesImpl>(); @@ -177,8 +170,8 @@ class ApplicationContext extends Context { private Resources.Theme mTheme = null; private PackageManager mPackageManager; private NotificationManager mNotificationManager = null; - private AccessibilityManager mAccessibilityManager = null; private ActivityManager mActivityManager = null; + private WallpaperManager mWallpaperManager = null; private Context mReceiverRestrictedContext = null; private SearchManager mSearchManager = null; private SensorManager mSensorManager = null; @@ -188,6 +181,7 @@ class ApplicationContext extends Context { private TelephonyManager mTelephonyManager = null; private ClipboardManager mClipboardManager = null; private boolean mRestricted; + private AccountManager mAccountManager; // protected by mSync private final Object mSync = new Object(); @@ -198,9 +192,6 @@ class ApplicationContext extends Context { private File mCacheDir; - private Drawable mWallpaper; - private IWallpaperServiceCallback mWallpaperCallback = null; - private static long sInstanceCount = 0; private static final String[] EMPTY_FILE_LIST = {}; @@ -520,127 +511,37 @@ class ApplicationContext extends Context { @Override public Drawable getWallpaper() { - Drawable dr = peekWallpaper(); - return dr != null ? dr : getResources().getDrawable( - com.android.internal.R.drawable.default_wallpaper); + return getWallpaperManager().getDrawable(); } @Override - public synchronized Drawable peekWallpaper() { - if (mWallpaper != null) { - return mWallpaper; - } - mWallpaperCallback = new WallpaperCallback(this); - mWallpaper = getCurrentWallpaperLocked(); - return mWallpaper; - } - - private Drawable getCurrentWallpaperLocked() { - try { - ParcelFileDescriptor fd = getWallpaperService().getWallpaper(mWallpaperCallback); - if (fd != null) { - Bitmap bm = BitmapFactory.decodeFileDescriptor(fd.getFileDescriptor()); - if (bm != null) { - // For now clear the density until we figure out how - // to deal with it for wallpapers. - bm.setDensity(0); - return new BitmapDrawable(getResources(), bm); - } - } - } catch (RemoteException e) { - } - return null; + public Drawable peekWallpaper() { + return getWallpaperManager().peekDrawable(); } @Override public int getWallpaperDesiredMinimumWidth() { - try { - return getWallpaperService().getWidthHint(); - } catch (RemoteException e) { - // Shouldn't happen! - return 0; - } + return getWallpaperManager().getDesiredMinimumWidth(); } @Override public int getWallpaperDesiredMinimumHeight() { - try { - return getWallpaperService().getHeightHint(); - } catch (RemoteException e) { - // Shouldn't happen! - return 0; - } + return getWallpaperManager().getDesiredMinimumHeight(); } @Override public void setWallpaper(Bitmap bitmap) throws IOException { - try { - ParcelFileDescriptor fd = getWallpaperService().setWallpaper(); - if (fd == null) { - return; - } - FileOutputStream fos = null; - try { - fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); - bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos); - } finally { - if (fos != null) { - fos.close(); - } - } - } catch (RemoteException e) { - } + getWallpaperManager().setBitmap(bitmap); } @Override public void setWallpaper(InputStream data) throws IOException { - try { - ParcelFileDescriptor fd = getWallpaperService().setWallpaper(); - if (fd == null) { - return; - } - FileOutputStream fos = null; - try { - fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); - setWallpaper(data, fos); - } finally { - if (fos != null) { - fos.close(); - } - } - } catch (RemoteException e) { - } - } - - private void setWallpaper(InputStream data, FileOutputStream fos) - throws IOException { - byte[] buffer = new byte[32768]; - int amt; - while ((amt=data.read(buffer)) > 0) { - fos.write(buffer, 0, amt); - } + getWallpaperManager().setStream(data); } @Override public void clearWallpaper() throws IOException { - try { - /* Set the wallpaper to the default values */ - ParcelFileDescriptor fd = getWallpaperService().setWallpaper(); - if (fd != null) { - FileOutputStream fos = null; - try { - fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); - setWallpaper(getResources().openRawResource( - com.android.internal.R.drawable.default_wallpaper), - fos); - } finally { - if (fos != null) { - fos.close(); - } - } - } - } catch (RemoteException e) { - } + getWallpaperManager().clear(); } @Override @@ -656,6 +557,27 @@ class ApplicationContext extends Context { } @Override + public void startIntentSender(IntentSender intent, + Intent fillInIntent, int flagsMask, int flagsValues, int extraFlags) + throws IntentSender.SendIntentException { + try { + String resolvedType = null; + if (fillInIntent != null) { + resolvedType = fillInIntent.resolveTypeIfNeeded(getContentResolver()); + } + int result = ActivityManagerNative.getDefault() + .startActivityIntentSender(mMainThread.getApplicationThread(), intent, + fillInIntent, resolvedType, null, null, + 0, flagsMask, flagsValues); + if (result == IActivityManager.START_CANCELED) { + throw new IntentSender.SendIntentException(); + } + Instrumentation.checkStartActivityResult(result, null); + } catch (RemoteException e) { + } + } + + @Override public void sendBroadcast(Intent intent) { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); try { @@ -733,6 +655,38 @@ class ApplicationContext extends Context { } @Override + public void sendStickyOrderedBroadcast(Intent intent, + BroadcastReceiver resultReceiver, + Handler scheduler, int initialCode, String initialData, + Bundle initialExtras) { + IIntentReceiver rd = null; + if (resultReceiver != null) { + if (mPackageInfo != null) { + if (scheduler == null) { + scheduler = mMainThread.getHandler(); + } + rd = mPackageInfo.getReceiverDispatcher( + resultReceiver, getOuterContext(), scheduler, + mMainThread.getInstrumentation(), false); + } else { + if (scheduler == null) { + scheduler = mMainThread.getHandler(); + } + rd = new ActivityThread.PackageInfo.ReceiverDispatcher( + resultReceiver, getOuterContext(), scheduler, null, false).getIIntentReceiver(); + } + } + String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); + try { + ActivityManagerNative.getDefault().broadcastIntent( + mMainThread.getApplicationThread(), intent, resolvedType, rd, + initialCode, initialData, initialExtras, null, + true, true); + } catch (RemoteException e) { + } + } + + @Override public void removeStickyBroadcast(Intent intent) { String resolvedType = intent.resolveTypeIfNeeded(getContentResolver()); if (resolvedType != null) { @@ -901,8 +855,12 @@ class ApplicationContext extends Context { } } else if (ACTIVITY_SERVICE.equals(name)) { return getActivityManager(); + } else if (INPUT_METHOD_SERVICE.equals(name)) { + return InputMethodManager.getInstance(this); } else if (ALARM_SERVICE.equals(name)) { return getAlarmManager(); + } else if (ACCOUNT_SERVICE.equals(name)) { + return getAccountManager(); } else if (POWER_SERVICE.equals(name)) { return getPowerManager(); } else if (CONNECTIVITY_SERVICE.equals(name)) { @@ -919,10 +877,8 @@ class ApplicationContext extends Context { return getLocationManager(); } else if (SEARCH_SERVICE.equals(name)) { return getSearchManager(); - } else if ( SENSOR_SERVICE.equals(name)) { + } else if (SENSOR_SERVICE.equals(name)) { return getSensorManager(); - } else if (BLUETOOTH_SERVICE.equals(name)) { - return getBluetoothDevice(); } else if (VIBRATOR_SERVICE.equals(name)) { return getVibrator(); } else if (STATUS_BAR_SERVICE.equals(name)) { @@ -938,13 +894,24 @@ class ApplicationContext extends Context { return getTelephonyManager(); } else if (CLIPBOARD_SERVICE.equals(name)) { return getClipboardManager(); - } else if (INPUT_METHOD_SERVICE.equals(name)) { - return InputMethodManager.getInstance(this); + } else if (WALLPAPER_SERVICE.equals(name)) { + return getWallpaperManager(); } return null; } + private AccountManager getAccountManager() { + synchronized (mSync) { + if (mAccountManager == null) { + IBinder b = ServiceManager.getService(ACCOUNT_SERVICE); + IAccountManager service = IAccountManager.Stub.asInterface(b); + mAccountManager = new AccountManager(this, service); + } + return mAccountManager; + } + } + private ActivityManager getActivityManager() { synchronized (mSync) { if (mActivityManager == null) { @@ -1001,8 +968,7 @@ class ApplicationContext extends Context { return sWifiManager; } - private NotificationManager getNotificationManager() - { + private NotificationManager getNotificationManager() { synchronized (mSync) { if (mNotificationManager == null) { mNotificationManager = new NotificationManager( @@ -1013,6 +979,16 @@ class ApplicationContext extends Context { return mNotificationManager; } + private WallpaperManager getWallpaperManager() { + synchronized (mSync) { + if (mWallpaperManager == null) { + mWallpaperManager = new WallpaperManager(getOuterContext(), + mMainThread.getHandler()); + } + } + return mWallpaperManager; + } + private TelephonyManager getTelephonyManager() { synchronized (mSync) { if (mTelephonyManager == null) { @@ -1052,23 +1028,6 @@ class ApplicationContext extends Context { return mSearchManager; } - private BluetoothDevice getBluetoothDevice() { - if (sIsBluetoothDeviceCached) { - return sBluetoothDevice; - } - synchronized (sSync) { - IBinder b = ServiceManager.getService(BLUETOOTH_SERVICE); - if (b == null) { - sBluetoothDevice = null; - } else { - IBluetoothDevice service = IBluetoothDevice.Stub.asInterface(b); - sBluetoothDevice = new BluetoothDevice(service); - } - sIsBluetoothDeviceCached = true; - } - return sBluetoothDevice; - } - private SensorManager getSensorManager() { synchronized (mSync) { if (mSensorManager == null) { @@ -1087,16 +1046,6 @@ class ApplicationContext extends Context { return mVibrator; } - private IWallpaperService getWallpaperService() { - synchronized (sSync) { - if (sWallpaperService == null) { - IBinder b = ServiceManager.getService(WALLPAPER_SERVICE); - sWallpaperService = IWallpaperService.Stub.asInterface(b); - } - } - return sWallpaperService; - } - private AudioManager getAudioManager() { if (mAudioManager == null) { @@ -1710,6 +1659,24 @@ class ApplicationContext extends Context { } @Override + public FeatureInfo[] getSystemAvailableFeatures() { + try { + return mPM.getSystemAvailableFeatures(); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override + public boolean hasSystemFeature(String name) { + try { + return mPM.hasSystemFeature(name); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override public int checkPermission(String permName, String pkgName) { try { return mPM.checkPermission(permName, pkgName); @@ -1746,6 +1713,15 @@ class ApplicationContext extends Context { } @Override + public int checkSignatures(int uid1, int uid2) { + try { + return mPM.checkUidSignatures(uid1, uid2); + } catch (RemoteException e) { + throw new RuntimeException("Package manager has died", e); + } + } + + @Override public String[] getPackagesForUid(int uid) { try { return mPM.getPackagesForUid(uid); @@ -2766,6 +2742,7 @@ class ApplicationContext extends Context { if (mFile.exists()) { if (!mFile.renameTo(mBackupFile)) { Log.e(TAG, "Couldn't rename file " + mFile + " to backup file " + mBackupFile); + return false; } } @@ -2801,25 +2778,4 @@ class ApplicationContext extends Context { return false; } } - - private static class WallpaperCallback extends IWallpaperServiceCallback.Stub { - private WeakReference<ApplicationContext> mContext; - - public WallpaperCallback(ApplicationContext context) { - mContext = new WeakReference<ApplicationContext>(context); - } - - public synchronized void onWallpaperChanged() { - - /* The wallpaper has changed but we shouldn't eagerly load the - * wallpaper as that would be inefficient. Reset the cached wallpaper - * to null so if the user requests the wallpaper again then we'll - * fetch it. - */ - final ApplicationContext applicationContext = mContext.get(); - if (applicationContext != null) { - applicationContext.mWallpaper = null; - } - } - } } diff --git a/core/java/android/app/ApplicationErrorReport.java b/core/java/android/app/ApplicationErrorReport.java index 6b17236..aeae5f9 100644 --- a/core/java/android/app/ApplicationErrorReport.java +++ b/core/java/android/app/ApplicationErrorReport.java @@ -171,6 +171,11 @@ public class ApplicationErrorReport implements Parcelable { public String throwMethodName; /** + * Line number the exception was thrown from. + */ + public int throwLineNumber; + + /** * Stack trace. */ public String stackTrace; @@ -190,6 +195,7 @@ public class ApplicationErrorReport implements Parcelable { throwFileName = in.readString(); throwClassName = in.readString(); throwMethodName = in.readString(); + throwLineNumber = in.readInt(); stackTrace = in.readString(); } @@ -202,6 +208,7 @@ public class ApplicationErrorReport implements Parcelable { dest.writeString(throwFileName); dest.writeString(throwClassName); dest.writeString(throwMethodName); + dest.writeInt(throwLineNumber); dest.writeString(stackTrace); } @@ -214,6 +221,7 @@ public class ApplicationErrorReport implements Parcelable { pw.println(prefix + "throwFileName: " + throwFileName); pw.println(prefix + "throwClassName: " + throwClassName); pw.println(prefix + "throwMethodName: " + throwMethodName); + pw.println(prefix + "throwLineNumber: " + throwLineNumber); pw.println(prefix + "stackTrace: " + stackTrace); } } diff --git a/core/java/android/app/ApplicationThreadNative.java b/core/java/android/app/ApplicationThreadNative.java index a3c6325..a772a8f 100644 --- a/core/java/android/app/ApplicationThreadNative.java +++ b/core/java/android/app/ApplicationThreadNative.java @@ -26,6 +26,7 @@ import android.content.pm.ServiceInfo; import android.content.res.Configuration; import android.os.Binder; import android.os.Bundle; +import android.os.Debug; import android.os.Parcelable; import android.os.RemoteException; import android.os.IBinder; @@ -206,8 +207,14 @@ public abstract class ApplicationThreadNative extends Binder data.enforceInterface(IApplicationThread.descriptor); IBinder token = data.readStrongBinder(); int startId = data.readInt(); - Intent args = Intent.CREATOR.createFromParcel(data); - scheduleServiceArgs(token, startId, args); + int fl = data.readInt(); + Intent args; + if (data.readInt() != 0) { + args = Intent.CREATOR.createFromParcel(data); + } else { + args = null; + } + scheduleServiceArgs(token, startId, fl, args); return true; } @@ -251,6 +258,13 @@ public abstract class ApplicationThreadNative extends Binder return true; } + case SCHEDULE_SUICIDE_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + scheduleSuicide(); + return true; + } + case REQUEST_THUMBNAIL_TRANSACTION: { data.enforceInterface(IApplicationThread.descriptor); @@ -303,8 +317,9 @@ public abstract class ApplicationThreadNative extends Binder String dataStr = data.readString(); Bundle extras = data.readBundle(); boolean ordered = data.readInt() != 0; + boolean sticky = data.readInt() != 0; scheduleRegisteredReceiver(receiver, intent, - resultCode, dataStr, extras, ordered); + resultCode, dataStr, extras, ordered, sticky); return true; } @@ -364,6 +379,16 @@ public abstract class ApplicationThreadNative extends Binder scheduleDestroyBackupAgent(appInfo); return true; } + + case GET_MEMORY_INFO_TRANSACTION: + { + data.enforceInterface(IApplicationThread.descriptor); + Debug.MemoryInfo mi = new Debug.MemoryInfo(); + getMemoryInfo(mi); + reply.writeNoException(); + mi.writeToParcel(reply, 0); + return true; + } } return super.onTransact(code, data, reply, flags); @@ -524,7 +549,8 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeInterfaceToken(IApplicationThread.descriptor); app.writeToParcel(data, 0); data.writeInt(backupMode); - mRemote.transact(SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION, data, null, 0); + mRemote.transact(SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); data.recycle(); } @@ -532,7 +558,8 @@ class ApplicationThreadProxy implements IApplicationThread { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); app.writeToParcel(data, 0); - mRemote.transact(SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION, data, null, 0); + mRemote.transact(SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); data.recycle(); } @@ -571,12 +598,18 @@ class ApplicationThreadProxy implements IApplicationThread { } public final void scheduleServiceArgs(IBinder token, int startId, - Intent args) throws RemoteException { + int flags, Intent args) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); data.writeStrongBinder(token); data.writeInt(startId); - args.writeToParcel(data, 0); + data.writeInt(flags); + if (args != null) { + data.writeInt(1); + args.writeToParcel(data, 0); + } else { + data.writeInt(0); + } mRemote.transact(SCHEDULE_SERVICE_ARGS_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); @@ -627,7 +660,15 @@ class ApplicationThreadProxy implements IApplicationThread { IBinder.FLAG_ONEWAY); data.recycle(); } - + + public final void scheduleSuicide() throws RemoteException { + Parcel data = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + mRemote.transact(SCHEDULE_SUICIDE_TRANSACTION, data, null, + IBinder.FLAG_ONEWAY); + data.recycle(); + } + public final void requestThumbnail(IBinder token) throws RemoteException { Parcel data = Parcel.obtain(); @@ -676,7 +717,7 @@ class ApplicationThreadProxy implements IApplicationThread { } public void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, - int resultCode, String dataStr, Bundle extras, boolean ordered) + int resultCode, String dataStr, Bundle extras, boolean ordered, boolean sticky) throws RemoteException { Parcel data = Parcel.obtain(); data.writeInterfaceToken(IApplicationThread.descriptor); @@ -686,6 +727,7 @@ class ApplicationThreadProxy implements IApplicationThread { data.writeString(dataStr); data.writeBundle(extras); data.writeInt(ordered ? 1 : 0); + data.writeInt(sticky ? 1 : 0); mRemote.transact(SCHEDULE_REGISTERED_RECEIVER_TRANSACTION, data, null, IBinder.FLAG_ONEWAY); data.recycle(); @@ -742,5 +784,16 @@ class ApplicationThreadProxy implements IApplicationThread { IBinder.FLAG_ONEWAY); data.recycle(); } + + public void getMemoryInfo(Debug.MemoryInfo outInfo) throws RemoteException { + Parcel data = Parcel.obtain(); + Parcel reply = Parcel.obtain(); + data.writeInterfaceToken(IApplicationThread.descriptor); + mRemote.transact(GET_MEMORY_INFO_TRANSACTION, data, reply, 0); + reply.readException(); + outInfo.readFromParcel(reply); + data.recycle(); + reply.recycle(); + } } diff --git a/core/java/android/app/BackupAgent.java b/core/java/android/app/BackupAgent.java index 0ac8a1e..b207998 100644 --- a/core/java/android/app/BackupAgent.java +++ b/core/java/android/app/BackupAgent.java @@ -36,6 +36,7 @@ import java.io.IOException; */ public abstract class BackupAgent extends ContextWrapper { private static final String TAG = "BackupAgent"; + private static final boolean DEBUG = false; public BackupAgent() { super(null); @@ -116,7 +117,7 @@ public abstract class BackupAgent extends ContextWrapper { ParcelFileDescriptor data, ParcelFileDescriptor newState) throws RemoteException { // !!! TODO - real implementation; for now just invoke the callbacks directly - Log.v(TAG, "doBackup() invoked"); + if (DEBUG) Log.v(TAG, "doBackup() invoked"); BackupDataOutput output = new BackupDataOutput(data.getFileDescriptor()); try { BackupAgent.this.onBackup(oldState, output, newState); @@ -132,7 +133,7 @@ public abstract class BackupAgent extends ContextWrapper { public void doRestore(ParcelFileDescriptor data, int appVersionCode, ParcelFileDescriptor newState) throws RemoteException { // !!! TODO - real implementation; for now just invoke the callbacks directly - Log.v(TAG, "doRestore() invoked"); + if (DEBUG) Log.v(TAG, "doRestore() invoked"); BackupDataInput input = new BackupDataInput(data.getFileDescriptor()); try { BackupAgent.this.onRestore(input, appVersionCode, newState); diff --git a/core/java/android/app/Dialog.java b/core/java/android/app/Dialog.java index 9432755..58e8b32 100644 --- a/core/java/android/app/Dialog.java +++ b/core/java/android/app/Dialog.java @@ -21,6 +21,7 @@ import com.android.internal.policy.PolicyManager; import android.content.Context; import android.content.DialogInterface; import android.content.ComponentName; +import android.content.ContextWrapper; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; @@ -480,17 +481,15 @@ public class Dialog implements DialogInterface, Window.Callback, * * <p>If the focused view didn't want this event, this method is called. * - * <p>The default implementation handles KEYCODE_BACK to close the - * dialog. + * <p>The default implementation consumed the KEYCODE_BACK to later + * handle it in {@link #onKeyUp}. * * @see #onKeyUp * @see android.view.KeyEvent */ public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_BACK) { - if (mCancelable) { - cancel(); - } + event.startTracking(); return true; } @@ -498,12 +497,29 @@ public class Dialog implements DialogInterface, Window.Callback, } /** + * Default implementation of {@link KeyEvent.Callback#onKeyLongPress(int, KeyEvent) + * KeyEvent.Callback.onKeyLongPress()}: always returns false (doesn't handle + * the event). + */ + public boolean onKeyLongPress(int keyCode, KeyEvent event) { + return false; + } + + /** * A key was released. * + * <p>The default implementation handles KEYCODE_BACK to close the + * dialog. + * * @see #onKeyDown * @see KeyEvent */ public boolean onKeyUp(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK && event.isTracking() + && !event.isCanceled()) { + onBackPressed(); + return true; + } return false; } @@ -517,6 +533,17 @@ public class Dialog implements DialogInterface, Window.Callback, } /** + * Called when the dialog has detected the user's press of the back + * key. The default implementation simply cancels the dialog (only if + * it is cancelable), but you can override this to do whatever you want. + */ + public void onBackPressed() { + if (mCancelable) { + cancel(); + } + } + + /** * Called when a touch screen event was not handled by any of the views * under it. This is most useful to process touch events that happen outside * of your window bounds, where there is no view to receive it. @@ -576,6 +603,12 @@ public class Dialog implements DialogInterface, Window.Callback, public void onWindowFocusChanged(boolean hasFocus) { } + public void onAttachedToWindow() { + } + + public void onDetachedFromWindow() { + } + /** * Called to process key events. You can override this to intercept all * key events before they are dispatched to the window. Be sure to call @@ -592,7 +625,8 @@ public class Dialog implements DialogInterface, Window.Callback, if (mWindow.superDispatchKeyEvent(event)) { return true; } - return event.dispatch(this); + return event.dispatch(this, mDecor != null + ? mDecor.getKeyDispatcherState() : null, this); } /** @@ -795,14 +829,31 @@ public class Dialog implements DialogInterface, Window.Callback, // associate search with owner activity if possible (otherwise it will default to // global search). - final ComponentName appName = mOwnerActivity == null ? null - : mOwnerActivity.getComponentName(); + final ComponentName appName = getAssociatedActivity(); final boolean globalSearch = (appName == null); searchManager.startSearch(null, false, appName, null, globalSearch); dismiss(); return true; } + /** + * @return The activity associated with this dialog, or null if there is no assocaited activity. + */ + private ComponentName getAssociatedActivity() { + Activity activity = mOwnerActivity; + Context context = getContext(); + while (activity == null && context != null) { + if (context instanceof Activity) { + activity = (Activity) context; // found it! + } else { + context = (context instanceof ContextWrapper) ? + ((ContextWrapper) context).getBaseContext() : // unwrap one level + null; // done + } + } + return activity == null ? null : activity.getComponentName(); + } + /** * Request that key events come to this dialog. Use this if your diff --git a/core/java/android/app/IActivityManager.java b/core/java/android/app/IActivityManager.java index f6ef549..9f505ac 100644 --- a/core/java/android/app/IActivityManager.java +++ b/core/java/android/app/IActivityManager.java @@ -23,6 +23,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IIntentSender; import android.content.IIntentReceiver; +import android.content.IntentSender; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; import android.content.pm.IPackageDataObserver; @@ -30,6 +31,7 @@ import android.content.pm.ProviderInfo; import android.content.res.Configuration; import android.graphics.Bitmap; import android.net.Uri; +import android.os.Debug; import android.os.RemoteException; import android.os.IBinder; import android.os.IInterface; @@ -76,10 +78,16 @@ public interface IActivityManager extends IInterface { public static final int START_CLASS_NOT_FOUND = -2; public static final int START_FORWARD_AND_REQUEST_CONFLICT = -3; public static final int START_PERMISSION_DENIED = -4; + public static final int START_NOT_ACTIVITY = -5; + public static final int START_CANCELED = -6; public int startActivity(IApplicationThread caller, Intent intent, String resolvedType, Uri[] grantedUriPermissions, int grantedMode, IBinder resultTo, String resultWho, int requestCode, boolean onlyIfNeeded, boolean debug) throws RemoteException; + public int startActivityIntentSender(IApplicationThread caller, + IntentSender intent, Intent fillInIntent, String resolvedType, + IBinder resultTo, String resultWho, int requestCode, + int flagsMask, int flagsValues) throws RemoteException; public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent) throws RemoteException; public boolean finishActivity(IBinder token, int code, Intent data) @@ -101,7 +109,7 @@ public interface IActivityManager extends IInterface { public void setPersistent(IBinder token, boolean isPersistent) throws RemoteException; public void attachApplication(IApplicationThread app) throws RemoteException; /* oneway */ - public void activityIdle(IBinder token) throws RemoteException; + public void activityIdle(IBinder token, Configuration config) throws RemoteException; public void activityPaused(IBinder token, Bundle state) throws RemoteException; /* oneway */ public void activityStopped(IBinder token, @@ -125,13 +133,15 @@ public interface IActivityManager extends IInterface { public void finishOtherInstances(IBinder token, ComponentName className) throws RemoteException; /* oneway */ public void reportThumbnail(IBinder token, - Bitmap thumbnail, CharSequence description) throws RemoteException; + Bitmap thumbnail, CharSequence description) throws RemoteException; public ContentProviderHolder getContentProvider(IApplicationThread caller, - String name) throws RemoteException; + String name) throws RemoteException; public void removeContentProvider(IApplicationThread caller, - String name) throws RemoteException; + String name) throws RemoteException; public void publishContentProviders(IApplicationThread caller, - List<ContentProviderHolder> providers) throws RemoteException; + List<ContentProviderHolder> providers) throws RemoteException; + public PendingIntent getRunningServiceControlPanel(ComponentName service) + throws RemoteException; public ComponentName startService(IApplicationThread caller, Intent service, String resolvedType) throws RemoteException; public int stopService(IApplicationThread caller, Intent service, @@ -139,7 +149,7 @@ public interface IActivityManager extends IInterface { public boolean stopServiceToken(ComponentName className, IBinder token, int startId) throws RemoteException; public void setServiceForeground(ComponentName className, IBinder token, - boolean isForeground) throws RemoteException; + int id, Notification notification, boolean keepNotification) throws RemoteException; public int bindService(IApplicationThread caller, IBinder token, Intent service, String resolvedType, IServiceConnection connection, int flags) throws RemoteException; @@ -149,13 +159,15 @@ public interface IActivityManager extends IInterface { public void unbindFinished(IBinder token, Intent service, boolean doRebind) throws RemoteException; /* oneway */ - public void serviceDoneExecuting(IBinder token) throws RemoteException; + public void serviceDoneExecuting(IBinder token, int type, int startId, + int res) throws RemoteException; public IBinder peekService(Intent service, String resolvedType) throws RemoteException; public boolean bindBackupAgent(ApplicationInfo appInfo, int backupRestoreMode) throws RemoteException; public void backupAgentCreated(String packageName, IBinder agent) throws RemoteException; public void unbindBackupAgent(ApplicationInfo appInfo) throws RemoteException; + public void killApplicationProcess(String processName, int uid) throws RemoteException; public boolean startInstrumentation(ComponentName className, String profileFile, int flags, Bundle arguments, IInstrumentationWatcher watcher) @@ -230,7 +242,6 @@ public interface IActivityManager extends IInterface { // Special low-level communication with activity manager. public void startRunning(String pkg, String cls, String action, String data) throws RemoteException; - public void systemReady() throws RemoteException; // Returns 1 if the user wants to debug. public int handleApplicationError(IBinder app, int flags, /* 1 == can debug */ @@ -271,6 +282,12 @@ public interface IActivityManager extends IInterface { public void closeSystemDialogs(String reason) throws RemoteException; + public Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids) + throws RemoteException; + + public void overridePendingTransition(IBinder token, String packageName, + int enterAnim, int exitAnim) throws RemoteException; + /* * Private non-Binder interfaces */ @@ -362,7 +379,7 @@ public interface IActivityManager extends IInterface { int PUBLISH_CONTENT_PROVIDERS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29; int SET_PERSISTENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30; int FINISH_SUB_ACTIVITY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+31; - int SYSTEM_READY_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32; + int GET_RUNNING_SERVICE_CONTROL_PANEL_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32; int START_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+33; int STOP_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+34; int BIND_SERVICE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+35; @@ -427,4 +444,8 @@ public interface IActivityManager extends IInterface { int START_ACTIVITY_IN_PACKAGE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+94; int KILL_APPLICATION_WITH_UID_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+95; int CLOSE_SYSTEM_DIALOGS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+96; + int GET_PROCESS_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+97; + int KILL_APPLICATION_PROCESS_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+98; + int START_ACTIVITY_INTENT_SENDER_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+99; + int OVERRIDE_PENDING_TRANSITION_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+100; } diff --git a/core/java/android/app/IApplicationThread.java b/core/java/android/app/IApplicationThread.java index c915770..89a52fd 100644 --- a/core/java/android/app/IApplicationThread.java +++ b/core/java/android/app/IApplicationThread.java @@ -25,6 +25,7 @@ import android.content.pm.ProviderInfo; import android.content.pm.ServiceInfo; import android.content.res.Configuration; import android.os.Bundle; +import android.os.Debug; import android.os.ParcelFileDescriptor; import android.os.RemoteException; import android.os.IBinder; @@ -71,7 +72,8 @@ public interface IApplicationThread extends IInterface { Intent intent, boolean rebind) throws RemoteException; void scheduleUnbindService(IBinder token, Intent intent) throws RemoteException; - void scheduleServiceArgs(IBinder token, int startId, Intent args) throws RemoteException; + void scheduleServiceArgs(IBinder token, int startId, int flags, Intent args) + throws RemoteException; void scheduleStopService(IBinder token) throws RemoteException; static final int DEBUG_OFF = 0; static final int DEBUG_ON = 1; @@ -81,6 +83,7 @@ public interface IApplicationThread extends IInterface { IInstrumentationWatcher testWatcher, int debugMode, boolean restrictedBackupMode, Configuration config, Map<String, IBinder> services) throws RemoteException; void scheduleExit() throws RemoteException; + void scheduleSuicide() throws RemoteException; void requestThumbnail(IBinder token) throws RemoteException; void scheduleConfigurationChanged(Configuration config) throws RemoteException; void updateTimeZone() throws RemoteException; @@ -88,7 +91,7 @@ public interface IApplicationThread extends IInterface { void dumpService(FileDescriptor fd, IBinder servicetoken, String[] args) throws RemoteException; void scheduleRegisteredReceiver(IIntentReceiver receiver, Intent intent, - int resultCode, String data, Bundle extras, boolean ordered) + int resultCode, String data, Bundle extras, boolean ordered, boolean sticky) throws RemoteException; void scheduleLowMemory() throws RemoteException; void scheduleActivityConfigurationChanged(IBinder token) throws RemoteException; @@ -96,6 +99,7 @@ public interface IApplicationThread extends IInterface { void profilerControl(boolean start, String path, ParcelFileDescriptor fd) throws RemoteException; void setSchedulingGroup(int group) throws RemoteException; + void getMemoryInfo(Debug.MemoryInfo outInfo) throws RemoteException; String descriptor = "android.app.IApplicationThread"; @@ -129,4 +133,6 @@ public interface IApplicationThread extends IInterface { int SET_SCHEDULING_GROUP_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+28; int SCHEDULE_CREATE_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+29; int SCHEDULE_DESTROY_BACKUP_AGENT_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+30; + int GET_MEMORY_INFO_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+31; + int SCHEDULE_SUICIDE_TRANSACTION = IBinder.FIRST_CALL_TRANSACTION+32; } diff --git a/core/java/android/app/INotificationManager.aidl b/core/java/android/app/INotificationManager.aidl index c1035b6..4d5238c 100644 --- a/core/java/android/app/INotificationManager.aidl +++ b/core/java/android/app/INotificationManager.aidl @@ -24,11 +24,15 @@ import android.content.Intent; /** {@hide} */ interface INotificationManager { + /** @deprecated use {@link #enqueueNotificationWithTag} instead */ void enqueueNotification(String pkg, int id, in Notification notification, inout int[] idReceived); + /** @deprecated use {@link #cancelNotificationWithTag} instead */ void cancelNotification(String pkg, int id); void cancelAllNotifications(String pkg); void enqueueToast(String pkg, ITransientNotification callback, int duration); void cancelToast(String pkg, ITransientNotification callback); + void enqueueNotificationWithTag(String pkg, String tag, int id, in Notification notification, inout int[] idReceived); + void cancelNotificationWithTag(String pkg, String tag, int id); } diff --git a/core/java/android/app/ISearchManager.aidl b/core/java/android/app/ISearchManager.aidl index bd72544..a7d6378 100644 --- a/core/java/android/app/ISearchManager.aidl +++ b/core/java/android/app/ISearchManager.aidl @@ -36,6 +36,16 @@ interface ISearchManager { boolean globalSearch, ISearchManagerCallback searchManagerCallback, int ident); + + void triggerSearch(in String query, + in ComponentName launchActivity, + in Bundle appSearchData, + ISearchManagerCallback searchManagerCallback, + int ident); + void stopSearch(); + + boolean isVisible(); + } diff --git a/core/java/android/app/IWallpaperService.aidl b/core/java/android/app/IWallpaperManager.aidl index a332b1a..69f64a1 100644 --- a/core/java/android/app/IWallpaperService.aidl +++ b/core/java/android/app/IWallpaperManager.aidl @@ -16,21 +16,35 @@ package android.app; +import android.os.Bundle; import android.os.ParcelFileDescriptor; -import android.app.IWallpaperServiceCallback; +import android.app.IWallpaperManagerCallback; +import android.app.WallpaperInfo; +import android.content.ComponentName; /** @hide */ -interface IWallpaperService { +interface IWallpaperManager { /** * Set the wallpaper. */ - ParcelFileDescriptor setWallpaper(); + ParcelFileDescriptor setWallpaper(String name); + + /** + * Set the live wallpaper. + */ + void setWallpaperComponent(in ComponentName name); /** * Get the wallpaper. */ - ParcelFileDescriptor getWallpaper(IWallpaperServiceCallback cb); + ParcelFileDescriptor getWallpaper(IWallpaperManagerCallback cb, + out Bundle outParams); + + /** + * Get information about a live wallpaper. + */ + WallpaperInfo getWallpaperInfo(); /** * Clear the wallpaper. diff --git a/core/java/android/app/IWallpaperServiceCallback.aidl b/core/java/android/app/IWallpaperManagerCallback.aidl index 6086f40..991b2bc 100644 --- a/core/java/android/app/IWallpaperServiceCallback.aidl +++ b/core/java/android/app/IWallpaperManagerCallback.aidl @@ -17,13 +17,13 @@ package android.app; /** - * Callback interface used by IWallpaperService to send asynchronous + * Callback interface used by IWallpaperManager to send asynchronous * notifications back to its clients. Note that this is a * one-way interface so the server does not block waiting for the client. * * @hide */ -oneway interface IWallpaperServiceCallback { +oneway interface IWallpaperManagerCallback { /** * Called when the wallpaper has changed */ diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java index e31f4f8..b8c3aa3 100644 --- a/core/java/android/app/Instrumentation.java +++ b/core/java/android/app/Instrumentation.java @@ -24,6 +24,7 @@ import android.content.IntentFilter; import android.content.pm.ActivityInfo; import android.content.res.Configuration; import android.os.Bundle; +import android.os.PerformanceCollector; import android.os.RemoteException; import android.os.Debug; import android.os.IBinder; @@ -83,10 +84,8 @@ public class Instrumentation { private List<ActivityWaiter> mWaitingActivities; private List<ActivityMonitor> mActivityMonitors; private IInstrumentationWatcher mWatcher; - private long mPreCpuTime; - private long mStart; private boolean mAutomaticPerformanceSnapshots = false; - private Bundle mPrePerfMetrics = new Bundle(); + private PerformanceCollector mPerformanceCollector; private Bundle mPerfMetrics = new Bundle(); public Instrumentation() { @@ -191,96 +190,21 @@ public class Instrumentation { public void setAutomaticPerformanceSnapshots() { mAutomaticPerformanceSnapshots = true; + mPerformanceCollector = new PerformanceCollector(); } public void startPerformanceSnapshot() { - mStart = 0; if (!isProfiling()) { - // Add initial binder counts - Bundle binderCounts = getBinderCounts(); - for (String key: binderCounts.keySet()) { - addPerfMetricLong("pre_" + key, binderCounts.getLong(key)); - } - - // Force a GC and zero out the performance counters. Do this - // before reading initial CPU/wall-clock times so we don't include - // the cost of this setup in our final metrics. - startAllocCounting(); - - // Record CPU time up to this point, and start timing. Note: this - // must happen at the end of this method, otherwise the timing will - // include noise. - mStart = SystemClock.uptimeMillis(); - mPreCpuTime = Process.getElapsedCpuTime(); + mPerformanceCollector.beginSnapshot(null); } } public void endPerformanceSnapshot() { if (!isProfiling()) { - // Stop the timing. This must be done first before any other counting is stopped. - long cpuTime = Process.getElapsedCpuTime(); - long duration = SystemClock.uptimeMillis(); - - stopAllocCounting(); - - long nativeMax = Debug.getNativeHeapSize() / 1024; - long nativeAllocated = Debug.getNativeHeapAllocatedSize() / 1024; - long nativeFree = Debug.getNativeHeapFreeSize() / 1024; - - Debug.MemoryInfo memInfo = new Debug.MemoryInfo(); - Debug.getMemoryInfo(memInfo); - - Runtime runtime = Runtime.getRuntime(); - - long dalvikMax = runtime.totalMemory() / 1024; - long dalvikFree = runtime.freeMemory() / 1024; - long dalvikAllocated = dalvikMax - dalvikFree; - - // Add final binder counts - Bundle binderCounts = getBinderCounts(); - for (String key: binderCounts.keySet()) { - addPerfMetricLong(key, binderCounts.getLong(key)); - } - - // Add alloc counts - Bundle allocCounts = getAllocCounts(); - for (String key: allocCounts.keySet()) { - addPerfMetricLong(key, allocCounts.getLong(key)); - } - - addPerfMetricLong("execution_time", duration - mStart); - addPerfMetricLong("pre_cpu_time", mPreCpuTime); - addPerfMetricLong("cpu_time", cpuTime - mPreCpuTime); - - addPerfMetricLong("native_size", nativeMax); - addPerfMetricLong("native_allocated", nativeAllocated); - addPerfMetricLong("native_free", nativeFree); - addPerfMetricInt("native_pss", memInfo.nativePss); - addPerfMetricInt("native_private_dirty", memInfo.nativePrivateDirty); - addPerfMetricInt("native_shared_dirty", memInfo.nativeSharedDirty); - - addPerfMetricLong("java_size", dalvikMax); - addPerfMetricLong("java_allocated", dalvikAllocated); - addPerfMetricLong("java_free", dalvikFree); - addPerfMetricInt("java_pss", memInfo.dalvikPss); - addPerfMetricInt("java_private_dirty", memInfo.dalvikPrivateDirty); - addPerfMetricInt("java_shared_dirty", memInfo.dalvikSharedDirty); - - addPerfMetricInt("other_pss", memInfo.otherPss); - addPerfMetricInt("other_private_dirty", memInfo.otherPrivateDirty); - addPerfMetricInt("other_shared_dirty", memInfo.otherSharedDirty); - + mPerfMetrics = mPerformanceCollector.endSnapshot(); } } - private void addPerfMetricLong(String key, long value) { - mPerfMetrics.putLong("performance." + key, value); - } - - private void addPerfMetricInt(String key, int value) { - mPerfMetrics.putInt("performance." + key, value); - } - /** * Called when the instrumented application is stopping, after all of the * normal application cleanup has occurred. @@ -1468,7 +1392,7 @@ public class Instrumentation { mWatcher = watcher; } - /*package*/ static void checkStartActivityResult(int res, Intent intent) { + /*package*/ static void checkStartActivityResult(int res, Object intent) { if (res >= IActivityManager.START_SUCCESS) { return; } @@ -1476,10 +1400,10 @@ public class Instrumentation { switch (res) { case IActivityManager.START_INTENT_NOT_RESOLVED: case IActivityManager.START_CLASS_NOT_FOUND: - if (intent.getComponent() != null) + if (intent instanceof Intent && ((Intent)intent).getComponent() != null) throw new ActivityNotFoundException( "Unable to find explicit activity class " - + intent.getComponent().toShortString() + + ((Intent)intent).getComponent().toShortString() + "; have you declared this activity in your AndroidManifest.xml?"); throw new ActivityNotFoundException( "No Activity found to handle " + intent); @@ -1489,6 +1413,9 @@ public class Instrumentation { case IActivityManager.START_FORWARD_AND_REQUEST_CONFLICT: throw new AndroidRuntimeException( "FORWARD_RESULT_FLAG used while also requesting a result"); + case IActivityManager.START_NOT_ACTIVITY: + throw new IllegalArgumentException( + "PendingIntent is not an activity"); default: throw new AndroidRuntimeException("Unknown error code " + res + " when starting " + intent); diff --git a/core/java/android/app/IntentService.java b/core/java/android/app/IntentService.java index 2b12a2a..804c8eb 100644 --- a/core/java/android/app/IntentService.java +++ b/core/java/android/app/IntentService.java @@ -18,6 +18,7 @@ public abstract class IntentService extends Service { private volatile Looper mServiceLooper; private volatile ServiceHandler mServiceHandler; private String mName; + private boolean mRedelivery; private final class ServiceHandler extends Handler { public ServiceHandler(Looper looper) { @@ -36,6 +37,19 @@ public abstract class IntentService extends Service { mName = name; } + /** + * Control redelivery of intents. If called with true, + * {@link #onStartCommand(Intent, int, int)} will return + * {@link Service#START_REDELIVER_INTENT} instead of + * {@link Service#START_NOT_STICKY}, so that if this service's process + * is called while it is executing the Intent in + * {@link #onHandleIntent(Intent)}, then when later restarted the same Intent + * will be re-delivered to it, to retry its execution. + */ + public void setIntentRedelivery(boolean enabled) { + mRedelivery = enabled; + } + @Override public void onCreate() { super.onCreate(); @@ -48,7 +62,6 @@ public abstract class IntentService extends Service { @Override public void onStart(Intent intent, int startId) { - super.onStart(intent, startId); Message msg = mServiceHandler.obtainMessage(); msg.arg1 = startId; msg.obj = intent; @@ -56,6 +69,12 @@ public abstract class IntentService extends Service { } @Override + public int onStartCommand(Intent intent, int flags, int startId) { + onStart(intent, startId); + return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY; + } + + @Override public void onDestroy() { mServiceLooper.quit(); } diff --git a/core/java/android/app/LauncherActivity.java b/core/java/android/app/LauncherActivity.java index d788c43..0ece2fc 100644 --- a/core/java/android/app/LauncherActivity.java +++ b/core/java/android/app/LauncherActivity.java @@ -18,6 +18,7 @@ package android.app; import android.content.Context; import android.content.Intent; +import android.content.pm.ComponentInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; @@ -52,9 +53,9 @@ import java.util.List; * */ public abstract class LauncherActivity extends ListActivity { - Intent mIntent; PackageManager mPackageManager; + IconResizer mIconResizer; /** * An item in the list @@ -70,13 +71,17 @@ public abstract class LauncherActivity extends ListActivity { ListItem(PackageManager pm, ResolveInfo resolveInfo, IconResizer resizer) { this.resolveInfo = resolveInfo; label = resolveInfo.loadLabel(pm); - if (label == null && resolveInfo.activityInfo != null) { + ComponentInfo ci = resolveInfo.activityInfo; + if (ci == null) ci = resolveInfo.serviceInfo; + if (label == null && ci != null) { label = resolveInfo.activityInfo.name; } - icon = resizer.createIconThumbnail(resolveInfo.loadIcon(pm)); - packageName = resolveInfo.activityInfo.applicationInfo.packageName; - className = resolveInfo.activityInfo.name; + if (resizer != null) { + icon = resizer.createIconThumbnail(resolveInfo.loadIcon(pm)); + } + packageName = ci.applicationInfo.packageName; + className = ci.name; } public ListItem() { @@ -90,13 +95,15 @@ public abstract class LauncherActivity extends ListActivity { private final Object lock = new Object(); private ArrayList<ListItem> mOriginalValues; + protected final IconResizer mIconResizer; protected final LayoutInflater mInflater; protected List<ListItem> mActivitiesList; private Filter mFilter; - public ActivityAdapter() { + public ActivityAdapter(IconResizer resizer) { + mIconResizer = resizer; mInflater = (LayoutInflater) LauncherActivity.this.getSystemService( Context.LAYOUT_INFLATER_SERVICE); mActivitiesList = makeListItems(); @@ -151,6 +158,10 @@ public abstract class LauncherActivity extends ListActivity { private void bindView(View view, ListItem item) { TextView text = (TextView) view; text.setText(item.label); + if (item.icon == null) { + item.icon = mIconResizer.createIconThumbnail( + item.resolveInfo.loadIcon(getPackageManager())); + } text.setCompoundDrawablesWithIntrinsicBounds(item.icon, null, null, null); } @@ -325,12 +336,13 @@ public abstract class LauncherActivity extends ListActivity { requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); setProgressBarIndeterminateVisibility(true); - setContentView(com.android.internal.R.layout.activity_list); - + onSetContentView(); + mIconResizer = new IconResizer(); + mIntent = new Intent(getTargetIntent()); mIntent.setComponent(null); - mAdapter = new ActivityAdapter(); + mAdapter = new ActivityAdapter(mIconResizer); setListAdapter(mAdapter); getListView().setTextFilterEnabled(true); @@ -338,10 +350,17 @@ public abstract class LauncherActivity extends ListActivity { setProgressBarIndeterminateVisibility(false); } + /** + * Override to call setContentView() with your own content view to + * customize the list layout. + */ + protected void onSetContentView() { + setContentView(com.android.internal.R.layout.activity_list); + } + @Override protected void onListItemClick(ListView l, View v, int position, long id) { - Intent intent = ((ActivityAdapter)mAdapter).intentForPosition(position); - + Intent intent = intentForPosition(position); startActivity(intent); } @@ -374,21 +393,26 @@ public abstract class LauncherActivity extends ListActivity { } /** + * Perform query on package manager for list items. The default + * implementation queries for activities. + */ + protected List<ResolveInfo> onQueryPackageManager(Intent queryIntent) { + return mPackageManager.queryIntentActivities(queryIntent, /* no flags */ 0); + } + + /** * Perform the query to determine which results to show and return a list of them. */ public List<ListItem> makeListItems() { // Load all matching activities and sort correctly - List<ResolveInfo> list = mPackageManager.queryIntentActivities(mIntent, - /* no flags */ 0); + List<ResolveInfo> list = onQueryPackageManager(mIntent); Collections.sort(list, new ResolveInfo.DisplayNameComparator(mPackageManager)); - IconResizer resizer = new IconResizer(); - ArrayList<ListItem> result = new ArrayList<ListItem>(list.size()); int listSize = list.size(); for (int i = 0; i < listSize; i++) { ResolveInfo resolveInfo = list.get(i); - result.add(new ListItem(mPackageManager, resolveInfo, resizer)); + result.add(new ListItem(mPackageManager, resolveInfo, null)); } return result; diff --git a/core/java/android/app/Notification.java b/core/java/android/app/Notification.java index 9834c75..be5a7d3 100644 --- a/core/java/android/app/Notification.java +++ b/core/java/android/app/Notification.java @@ -257,6 +257,13 @@ public class Notification implements Parcelable */ public static final int FLAG_NO_CLEAR = 0x00000020; + /** + * Bit to be bitwise-ored into the {@link #flags} field that should be + * set if this notification represents a currently running service. This + * will normally be set for you by {@link Service#startForeground}. + */ + public static final int FLAG_FOREGROUND_SERVICE = 0x00000040; + public int flags; /** @@ -458,7 +465,9 @@ public class Notification implements Parcelable sb.append(this.vibrate[i]); sb.append(','); } - sb.append(this.vibrate[N]); + if (N != -1) { + sb.append(this.vibrate[N]); + } sb.append("]"); } else if ((this.defaults & DEFAULT_VIBRATE) != 0) { sb.append("default"); diff --git a/core/java/android/app/NotificationManager.java b/core/java/android/app/NotificationManager.java index 39edab7..6fe12fc 100644 --- a/core/java/android/app/NotificationManager.java +++ b/core/java/android/app/NotificationManager.java @@ -61,7 +61,8 @@ public class NotificationManager private static INotificationManager sService; - static private INotificationManager getService() + /** @hide */ + static public INotificationManager getService() { if (sService != null) { return sService; @@ -86,12 +87,27 @@ public class NotificationManager */ public void notify(int id, Notification notification) { + notify(null, id, notification); + } + + /** + * Persistent notification on the status bar, + * + * @param tag An string identifier for this notification unique within your + * application. + * @param notification A {@link Notification} object describing how to + * notify the user, other than the view you're providing. Must not be null. + * @return the id of the notification that is associated with the string identifier that + * can be used to cancel the notification + */ + public void notify(String tag, int id, Notification notification) + { int[] idOut = new int[1]; INotificationManager service = getService(); String pkg = mContext.getPackageName(); if (localLOGV) Log.v(TAG, pkg + ": notify(" + id + ", " + notification + ")"); try { - service.enqueueNotification(pkg, id, notification, idOut); + service.enqueueNotificationWithTag(pkg, tag, id, notification, idOut); if (id != idOut[0]) { Log.w(TAG, "notify: id corrupted: sent " + id + ", got back " + idOut[0]); } @@ -106,11 +122,21 @@ public class NotificationManager */ public void cancel(int id) { + cancel(null, id); + } + + /** + * Cancel a previously shown notification. If it's transient, the view + * will be hidden. If it's persistent, it will be removed from the status + * bar. + */ + public void cancel(String tag, int id) + { INotificationManager service = getService(); String pkg = mContext.getPackageName(); if (localLOGV) Log.v(TAG, pkg + ": cancel(" + id + ")"); try { - service.cancelNotification(pkg, id); + service.cancelNotificationWithTag(pkg, tag, id); } catch (RemoteException e) { } } diff --git a/core/java/android/app/PendingIntent.java b/core/java/android/app/PendingIntent.java index f7479bc..be1dc4a 100644 --- a/core/java/android/app/PendingIntent.java +++ b/core/java/android/app/PendingIntent.java @@ -147,7 +147,7 @@ public final class PendingIntent implements Parcelable { mHandler = handler; } public void performReceive(Intent intent, int resultCode, - String data, Bundle extras, boolean serialized) { + String data, Bundle extras, boolean serialized, boolean sticky) { mIntent = intent; mResultCode = resultCode; mResultData = data; @@ -519,7 +519,8 @@ public final class PendingIntent implements Parcelable { mTarget = IIntentSender.Stub.asInterface(target); } - /*package*/ IIntentSender getTarget() { + /** @hide */ + public IIntentSender getTarget() { return mTarget; } } diff --git a/core/java/android/app/SearchDialog.java b/core/java/android/app/SearchDialog.java index 5844079..e5a769b 100644 --- a/core/java/android/app/SearchDialog.java +++ b/core/java/android/app/SearchDialog.java @@ -30,7 +30,6 @@ import android.content.pm.ResolveInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Resources; import android.database.Cursor; -import android.graphics.drawable.Animatable; import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; @@ -51,7 +50,6 @@ import android.util.Log; import android.view.ContextThemeWrapper; import android.view.Gravity; import android.view.KeyEvent; -import android.view.Menu; import android.view.MotionEvent; import android.view.View; import android.view.ViewConfiguration; @@ -65,9 +63,9 @@ import android.widget.AutoCompleteTextView; import android.widget.Button; import android.widget.ImageButton; import android.widget.ImageView; +import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; -import android.widget.ListAdapter; import android.widget.AdapterView.OnItemClickListener; import android.widget.AdapterView.OnItemSelectedListener; @@ -98,6 +96,10 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // 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"; + + // The string used for privateImeOptions to identify to the IME that it should not show + // a microphone button since one already exists in the search dialog. + private static final String IME_OPTION_NO_MICROPHONE = "nm"; private static final int SEARCH_PLATE_LEFT_PADDING_GLOBAL = 12; private static final int SEARCH_PLATE_LEFT_PADDING_NON_GLOBAL = 7; @@ -129,8 +131,8 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS private ArrayList<ComponentName> mPreviousComponents; // For voice searching - private Intent mVoiceWebSearchIntent; - private Intent mVoiceAppSearchIntent; + private final Intent mVoiceWebSearchIntent; + private final Intent mVoiceAppSearchIntent; // support for AutoCompleteTextView suggestions display private SuggestionsAdapter mSuggestionsAdapter; @@ -158,18 +160,25 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS */ public SearchDialog(Context context) { super(context, com.android.internal.R.style.Theme_GlobalSearchBar); + + // Save voice intent for later queries/launching + mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); + mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, + RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH); + + mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); + mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); } /** - * We create the search dialog just once, and it stays around (hidden) - * until activated by the user. + * Create the search dialog and any resources that are used for the + * entire lifetime of the dialog. */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - setContentView(com.android.internal.R.layout.search_bar); - Window theWindow = getWindow(); WindowManager.LayoutParams lp = theWindow.getAttributes(); lp.type = WindowManager.LayoutParams.TYPE_SEARCH_BAR; @@ -182,7 +191,21 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS lp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE; theWindow.setAttributes(lp); + // Touching outside of the search dialog will dismiss it + setCanceledOnTouchOutside(true); + } + + /** + * We recreate the dialog view each time it becomes visible so as to limit + * the scope of any problems with the contained resources. + */ + private void createContentView() { + setContentView(com.android.internal.R.layout.search_bar); + // get the view elements for local access + SearchBar searchBar = (SearchBar) findViewById(com.android.internal.R.id.search_bar); + searchBar.setSearchDialog(this); + mBadgeLabel = (TextView) findViewById(com.android.internal.R.id.search_badge); mSearchAutoComplete = (SearchAutoComplete) findViewById(com.android.internal.R.id.search_src_text); @@ -192,7 +215,10 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS mSearchPlate = findViewById(com.android.internal.R.id.search_plate); mWorkingSpinner = getContext().getResources(). getDrawable(com.android.internal.R.drawable.search_spinner); - + mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds( + null, null, mWorkingSpinner, null); + setWorking(false); + // attach listeners mSearchAutoComplete.addTextChangedListener(mTextWatcher); mSearchAutoComplete.setOnKeyListener(mTextKeyListener); @@ -203,25 +229,10 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS mVoiceButton.setOnClickListener(mVoiceButtonClickListener); mVoiceButton.setOnKeyListener(mButtonsKeyListener); - mSearchAutoComplete.setSearchDialog(this); - // pre-hide all the extraneous elements mBadgeLabel.setVisibility(View.GONE); // Additional adjustments to make Dialog work for Search - - // Touching outside of the search dialog will dismiss it - setCanceledOnTouchOutside(true); - - // Save voice intent for later queries/launching - mVoiceWebSearchIntent = new Intent(RecognizerIntent.ACTION_WEB_SEARCH); - mVoiceWebSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mVoiceWebSearchIntent.putExtra(RecognizerIntent.EXTRA_LANGUAGE_MODEL, - RecognizerIntent.LANGUAGE_MODEL_WEB_SEARCH); - - mVoiceAppSearchIntent = new Intent(RecognizerIntent.ACTION_RECOGNIZE_SPEECH); - mVoiceAppSearchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - mSearchAutoCompleteImeOptions = mSearchAutoComplete.getImeOptions(); } @@ -356,9 +367,13 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // isDefaultSearchable() should always give the same result. mGlobalSearchMode = globalSearch || searchManager.isDefaultSearchable(mSearchable); mActivityContext = mSearchable.getActivityContext(getContext()); - + // show the dialog. this will call onStart(). - if (!isShowing()) { + if (!isShowing()) { + // Recreate the search bar view every time the dialog is shown, to get rid + // of any bad state in the AutoCompleteTextView etc + createContentView(); + // 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 @@ -408,15 +423,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS * @param working true to show spinner, false to hide spinner */ public void setWorking(boolean working) { - if (working) { - mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds( - null, null, mWorkingSpinner, null); - ((Animatable) mWorkingSpinner).start(); - } else { - mSearchAutoComplete.setCompoundDrawablesWithIntrinsicBounds( - null, null, null, null); - ((Animatable) mWorkingSpinner).stop(); - } + mWorkingSpinner.setAlpha(working ? 255 : 0); + mWorkingSpinner.setVisible(working, false); + mWorkingSpinner.invalidateSelf(); } /** @@ -502,6 +511,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS updateSearchAppIcon(); updateSearchBadge(); updateQueryHint(); + mSearchAutoComplete.showDropDownAfterLayout(); } } @@ -537,6 +547,14 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS mSearchAutoComplete.setInputType(inputType); mSearchAutoCompleteImeOptions = mSearchable.getImeOptions(); mSearchAutoComplete.setImeOptions(mSearchAutoCompleteImeOptions); + + // If the search dialog is going to show a voice search button, then don't let + // the soft keyboard display a microphone button if it would have otherwise. + if (mSearchable.getVoiceSearchEnabled()) { + mSearchAutoComplete.setPrivateImeOptions(IME_OPTION_NO_MICROPHONE); + } else { + mSearchAutoComplete.setPrivateImeOptions(null); + } } } @@ -740,17 +758,11 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS return false; } - // handle back key to go back to previous searchable, etc. - if (handleBackKey(keyCode, event)) { + if (keyCode == KeyEvent.KEYCODE_SEARCH && event.getRepeatCount() == 0) { + event.startTracking(); + // Consume search key for later use. return true; } - - if (keyCode == KeyEvent.KEYCODE_SEARCH) { - // If the search key is pressed, toggle between global and in-app search. If we are - // currently doing global search and there is no in-app search context to toggle to, - // just don't do anything. - return toggleGlobalSearch(); - } // if it's an action specified by the searchable activity, launch the // entered query with the action key @@ -759,8 +771,26 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS launchQuerySearch(keyCode, actionKey.getQueryActionMsg()); return true; } - - return false; + + return super.onKeyDown(keyCode, event); + } + + @Override + public boolean onKeyUp(int keyCode, KeyEvent event) { + if (DBG) Log.d(LOG_TAG, "onKeyUp(" + keyCode + "," + event + ")"); + if (mSearchable == null) { + return false; + } + + if (keyCode == KeyEvent.KEYCODE_SEARCH && event.isTracking() + && !event.isCanceled()) { + // If the search key is pressed, toggle between global and in-app search. If we are + // currently doing global search and there is no in-app search context to toggle to, + // just don't do anything. + return toggleGlobalSearch(); + } + + return super.onKeyUp(keyCode, event); } /** @@ -1120,7 +1150,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS /** * Launch a search for the text in the query text field. */ - protected void launchQuerySearch() { + public void launchQuerySearch() { launchQuerySearch(KeyEvent.KEYCODE_UNKNOWN, null); } @@ -1136,7 +1166,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS String query = mSearchAutoComplete.getText().toString(); String action = mGlobalSearchMode ? Intent.ACTION_WEB_SEARCH : Intent.ACTION_SEARCH; Intent intent = createIntent(action, null, null, query, null, - actionKey, actionMsg); + actionKey, actionMsg, null); + // Allow GlobalSearch to log and create shortcut for searches launched by + // the search button, enter key or an action key. + if (mGlobalSearchMode) { + mSuggestionsAdapter.reportSearch(query); + } launchIntent(intent); } @@ -1169,7 +1204,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // report back about the click if (mGlobalSearchMode) { // in global search mode, do it via cursor - mSuggestionsAdapter.callCursorOnClick(c, position); + mSuggestionsAdapter.callCursorOnClick(c, position, actionKey, actionMsg); } else if (intent != null && mPreviousComponents != null && !mPreviousComponents.isEmpty()) { @@ -1206,7 +1241,7 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS cv.put(SearchManager.SUGGEST_COLUMN_INTENT_ACTION, intent.getAction()); cv.put(SearchManager.SUGGEST_COLUMN_INTENT_DATA, intent.getDataString()); cv.put(SearchManager.SUGGEST_COLUMN_INTENT_COMPONENT_NAME, - intent.getStringExtra(SearchManager.COMPONENT_NAME_KEY)); + intent.getComponent().flattenToShortString()); // ensure the icons will work for global search cv.put(SearchManager.SUGGEST_COLUMN_ICON_1, @@ -1292,6 +1327,12 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS // intent, and to avoid the extra step of going through GlobalSearch. if (mGlobalSearchMode) { launchGlobalSearchIntent(intent); + if (mStoredComponentName != null) { + // If we're embedded in an application, dismiss the dialog. + // This ensures that if the intent is handled by the current + // activity, it's not obscured by the dialog. + dismiss(); + } } else { // If the intent was created from a suggestion, it will always have an explicit // component here. @@ -1463,6 +1504,13 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS } /** + * Checks if there are any previous searchable components in the history stack. + */ + private boolean hasPreviousComponent() { + return mPreviousComponents != null && !mPreviousComponents.isEmpty(); + } + + /** * Saves the previous component that was searched, so that we can go * back to it. */ @@ -1480,14 +1528,10 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS * no previous component. */ private ComponentName popPreviousComponent() { - if (mPreviousComponents == null) { - return null; - } - int size = mPreviousComponents.size(); - if (size == 0) { + if (!hasPreviousComponent()) { return null; } - return mPreviousComponents.remove(size - 1); + return mPreviousComponents.remove(mPreviousComponents.size() - 1); } /** @@ -1500,17 +1544,17 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS if (previous == null) { return false; } + if (!show(previous, mAppSearchData, false)) { Log.w(LOG_TAG, "Failed to switch to source " + previous); return false; } - + // must touch text to trigger suggestions // TODO: should this be the text as it was when the user left // the source that we are now going back to? String query = mSearchAutoComplete.getText().toString(); setUserQuery(query); - return true; } @@ -1563,9 +1607,10 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS String query = getColumnString(c, SearchManager.SUGGEST_COLUMN_QUERY); String extraData = getColumnString(c, SearchManager.SUGGEST_COLUMN_INTENT_EXTRA_DATA); + String mode = mGlobalSearchMode ? SearchManager.MODE_GLOBAL_SEARCH_SUGGESTION : null; return createIntent(action, dataUri, extraData, query, componentName, actionKey, - actionMsg); + actionMsg, mode); } catch (RuntimeException e ) { int rowNum; try { // be really paranoid now @@ -1591,13 +1636,21 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS * or {@link KeyEvent#KEYCODE_UNKNOWN} if none. * @param actionMsg The message for the action key that was pressed, * or <code>null</code> if none. + * @param mode The search mode, one of the acceptable values for + * {@link SearchManager#SEARCH_MODE}, or {@code null}. * @return The intent. */ private Intent createIntent(String action, Uri data, String extraData, String query, - String componentName, int actionKey, String actionMsg) { + String componentName, int actionKey, String actionMsg, String mode) { // Now build the Intent Intent intent = new Intent(action); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + // We need CLEAR_TOP to avoid reusing an old task that has other activities + // on top of the one we want. We don't want to do this in in-app search though, + // as it can be destructive to the activity stack. + if (mGlobalSearchMode) { + intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + } if (data != null) { intent.setData(data); } @@ -1618,6 +1671,9 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS intent.putExtra(SearchManager.ACTION_KEY, actionKey); intent.putExtra(SearchManager.ACTION_MSG, actionMsg); } + if (mode != null) { + intent.putExtra(SearchManager.SEARCH_MODE, mode); + } // Only allow 3rd-party intents from GlobalSearch if (!mGlobalSearchMode) { intent.setComponent(mSearchable.getSearchActivity()); @@ -1648,15 +1704,59 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS } return result; } - + + /** + * The root element in the search bar layout. This is a custom view just to override + * the handling of the back button. + */ + public static class SearchBar extends LinearLayout { + + private SearchDialog mSearchDialog; + + public SearchBar(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public SearchBar(Context context) { + super(context); + } + + public void setSearchDialog(SearchDialog searchDialog) { + mSearchDialog = searchDialog; + } + + /** + * Overrides the handling of the back key to move back to the previous sources or dismiss + * the search dialog, instead of dismissing the input method. + */ + @Override + public boolean dispatchKeyEventPreIme(KeyEvent event) { + if (DBG) Log.d(LOG_TAG, "onKeyPreIme(" + event + ")"); + if (mSearchDialog != null && event.getKeyCode() == KeyEvent.KEYCODE_BACK) { + KeyEvent.DispatcherState state = getKeyDispatcherState(); + if (state != null) { + if (event.getAction() == KeyEvent.ACTION_DOWN + && event.getRepeatCount() == 0) { + state.startTracking(event, this); + return true; + } else if (event.getAction() == KeyEvent.ACTION_UP + && !event.isCanceled() && state.isTracking(event)) { + mSearchDialog.onBackPressed(); + return true; + } + } + } + return super.dispatchKeyEventPreIme(event); + } + } + /** * Local subclass for AutoCompleteTextView. */ public static class SearchAutoComplete extends AutoCompleteTextView { private int mThreshold; - private SearchDialog mSearchDialog; - + public SearchAutoComplete(Context context) { super(context); mThreshold = getThreshold(); @@ -1672,10 +1772,6 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS mThreshold = getThreshold(); } - private void setSearchDialog(SearchDialog searchDialog) { - mSearchDialog = searchDialog; - } - @Override public void setThreshold(int threshold) { super.setThreshold(threshold); @@ -1729,54 +1825,26 @@ public class SearchDialog extends Dialog implements OnItemClickListener, OnItemS return mThreshold <= 0 || super.enoughToFilter(); } - /** - * {@link AutoCompleteTextView#onKeyPreIme(int, KeyEvent)}) dismisses the drop-down on BACK, - * so we must override this method to modify the BACK behavior. - */ - @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; - } - // If the drop-down obscures the keyboard, the user wouldn't see anything - // happening when pressing back, so we dismiss the entire dialog instead. - // - // also: if there is no text entered, we also want to dismiss the whole dialog, - // not just the soft keyboard. the exception to this is if there are shortcuts - // that aren't displayed (e.g are being obscured by the soft keyboard); in that - // case we want to dismiss the soft keyboard so the user can see the rest of the - // shortcuts. - if (isInputMethodNotNeeded() || - (isEmpty() && getDropDownChildCount() >= getAdapterCount())) { - mSearchDialog.cancel(); - return true; - } - return false; // will dismiss soft keyboard if necessary - } - return false; - } + } - private int getAdapterCount() { - final ListAdapter adapter = getAdapter(); - return adapter == null ? 0 : adapter.getCount(); + @Override + public void onBackPressed() { + // If the input method is covering the search dialog completely, + // e.g. in landscape mode with no hard keyboard, dismiss just the input method + InputMethodManager imm = (InputMethodManager)getContext() + .getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm != null && imm.isFullscreenMode() && + imm.hideSoftInputFromWindow(getWindow().getDecorView().getWindowToken(), 0)) { + return; } - } - - protected boolean handleBackKey(int keyCode, KeyEvent event) { - if (keyCode == KeyEvent.KEYCODE_BACK && event.getAction() == KeyEvent.ACTION_DOWN) { - if (backToPreviousComponent()) { - return true; - } + // Otherwise, go back to any previous source (e.g. back to QSB when + // pivoted into a source. + if (!backToPreviousComponent()) { + // If no previous source, close search dialog cancel(); - return true; } - return false; } - + /** * Implements OnItemClickListener */ diff --git a/core/java/android/app/SearchManager.java b/core/java/android/app/SearchManager.java index 2245562..7f5a1e7 100644 --- a/core/java/android/app/SearchManager.java +++ b/core/java/android/app/SearchManager.java @@ -27,6 +27,7 @@ import android.os.Handler; import android.os.RemoteException; import android.os.ServiceManager; import android.server.search.SearchableInfo; +import android.text.TextUtils; import android.util.Log; import android.view.KeyEvent; @@ -329,8 +330,8 @@ import java.util.List; * you'll need to update your searchable activity (or other activities) to receive the intents * as you've defined them.</li> * <li>Implement a Content Provider that provides suggestions. If you already have one, and it - * has access to your suggestions data. If not, you'll have to create one. - * You'll also provide information about your Content Provider in your + * has access to your suggestions data, you can use that provider. If not, you'll have to create + * one. You'll also provide information about your Content Provider in your * package's <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">manifest</a>.</li> * <li>Update your searchable activity's XML configuration file. There are two categories of * information used for suggestions: @@ -768,8 +769,11 @@ import java.util.List; * </tr> * * <tr><th>android:icon</th> - * <td>If provided, this icon will be used <i>in place</i> of the label string. This - * is provided in order to present logos or other non-textual banners.</td> + * <td>If provided, this icon will be shown in place of the label above the search box. + * This is a reference to a drawable (icon) resource. Note that the application icon + * is also used as an icon to the left of the search box and you cannot modify this + * behavior, so including the icon attribute is unecessary and this may be + * deprecated in the future.</td> * <td align="center">No</td> * </tr> * @@ -778,11 +782,6 @@ import java.util.List; * entered.</td> * <td align="center">No</td> * </tr> - * - * <tr><th>android:searchButtonText</th> - * <td>If provided, this text will replace the default text in the "Search" button.</td> - * <td align="center">No</td> - * </tr> * * <tr><th>android:searchMode</th> * <td>If provided and non-zero, sets additional modes for control of the search @@ -791,15 +790,17 @@ import java.util.List; * <tbody> * <tr><th>showSearchLabelAsBadge</th> * <td>If set, this flag enables the display of the search target (label) - * within the search bar. If this flag and showSearchIconAsBadge + * above the search box. If this flag and showSearchIconAsBadge * (see below) are both not set, no badge will be shown.</td> * </tr> * <tr><th>showSearchIconAsBadge</th> - * <td>If set, this flag enables the display of the search target (icon) within - * the search bar. If this flag and showSearchLabelAsBadge + * <td>If set, this flag enables the display of the search target (icon) + * above the search box. If this flag and showSearchLabelAsBadge * (see above) are both not set, no badge will be shown. If both flags * are set, showSearchIconAsBadge has precedence and the icon will be - * shown.</td> + * shown. Because the application icon is now used to the left of the + * search box by default, using this search mode is no longer necessary + * and may be deprecated in the future.</td> * </tr> * <tr><th>queryRewriteFromData</th> * <td>If set, this flag causes the suggestion column SUGGEST_COLUMN_INTENT_DATA @@ -1180,7 +1181,7 @@ import java.util.List; * Bundle appData = new Bundle(); * appData.put...(); * appData.put...(); - * startSearch(null, false, appData); + * startSearch(null, false, appData, false); * return true; * }</pre> * @@ -1288,6 +1289,25 @@ public class SearchManager public final static String SOURCE = "source"; /** + * Intent extra data key: Use {@link android.content.Intent#getBundleExtra + * content.Intent.getBundleExtra(SEARCH_MODE)} to get the search mode used + * to launch the intent. + * The only current value for this is {@link #MODE_GLOBAL_SEARCH_SUGGESTION}. + * + * @hide + */ + public final static String SEARCH_MODE = "search_mode"; + + /** + * Value for the {@link #SEARCH_MODE} key. + * This is used if the intent was launched by clicking a suggestion in global search + * mode (Quick Search Box). + * + * @hide + */ + public static final String MODE_GLOBAL_SEARCH_SUGGESTION = "global_search_suggestion"; + + /** * Intent extra data key: Use this key with Intent.ACTION_SEARCH and * {@link android.content.Intent#getIntExtra content.Intent.getIntExtra()} * to obtain the keycode that the user used to trigger this query. It will be zero if the @@ -1342,6 +1362,10 @@ public class SearchManager = "DialogCursorProtocol.CLICK.sendPosition"; public final static String CLICK_SEND_MAX_DISPLAY_POS = "DialogCursorProtocol.CLICK.sendDisplayPosition"; + public final static String CLICK_SEND_ACTION_KEY + = "DialogCursorProtocol.CLICK.sendActionKey"; + public final static String CLICK_SEND_ACTION_MSG + = "DialogCursorProtocol.CLICK.sendActionMsg"; public final static String CLICK_RECEIVE_SELECTED_POS = "DialogCursorProtocol.CLICK.receiveSelectedPosition"; @@ -1349,6 +1373,14 @@ public class SearchManager * When the threshold received in {@link #POST_REFRESH_RECEIVE_DISPLAY_NOTIFY} is displayed. */ public final static int THRESH_HIT = 3; + + /** + * When a search is started without using a suggestion. + */ + public final static int SEARCH = 4; + public final static String SEARCH_SEND_MAX_DISPLAY_POS + = "DialogCursorProtocol.SEARCH.sendDisplayPosition"; + public final static String SEARCH_SEND_QUERY = "DialogCursorProtocol.SEARCH.query"; } /** @@ -1559,6 +1591,12 @@ public class SearchManager public final static String SUGGEST_NEVER_MAKE_SHORTCUT = "_-1"; /** + * Query parameter added to suggestion queries to limit the number of suggestions returned. + * This limit is only advisory and suggestion providers may chose to ignore it. + */ + public final static String SUGGEST_PARAMETER_LIMIT = "limit"; + + /** * If a suggestion has this value in {@link #SUGGEST_COLUMN_INTENT_ACTION}, * the search dialog will switch to a different suggestion source when the * suggestion is clicked. @@ -1636,7 +1674,17 @@ public class SearchManager private final Context mContext; + /** + * compact representation of the activity associated with this search manager so + * we can say who we are when starting search. the search managerservice, in turn, + * uses this to properly handle the back stack. + */ private int mIdent; + + /** + * The package associated with this seach manager. + */ + private String mAssociatedPackage; // package private since they are used by the inner class SearchManagerCallback /* package */ final Handler mHandler; @@ -1656,11 +1704,15 @@ public class SearchManager return mIdent != 0; } - /*package*/ void setIdent(int ident) { + /*package*/ void setIdent(int ident, ComponentName component) { if (mIdent != 0) { throw new IllegalStateException("mIdent already set"); } + if (component == null) { + throw new IllegalArgumentException("component must be non-null"); + } mIdent = ident; + mAssociatedPackage = component.getPackageName(); } /** @@ -1710,12 +1762,50 @@ public class SearchManager boolean globalSearch) { if (mIdent == 0) throw new IllegalArgumentException( "Called from outside of an Activity context"); + if (!globalSearch && !mAssociatedPackage.equals(launchActivity.getPackageName())) { + Log.w(TAG, "invoking app search on a different package " + + "not associated with this search manager"); + } try { // activate the search manager and start it up! mService.startSearch(initialQuery, selectInitialQuery, launchActivity, appSearchData, globalSearch, mSearchManagerCallback, mIdent); } catch (RemoteException ex) { - Log.e(TAG, "startSearch() failed: " + ex); + Log.e(TAG, "startSearch() failed.", ex); + } + } + + /** + * Similar to {@link #startSearch} but actually fires off the search query after invoking + * the search dialog. Made available for testing purposes. + * + * @param query The query to trigger. If empty, request will be ignored. + * @param launchActivity The ComponentName of the activity that has launched this search. + * @param appSearchData An application can insert application-specific + * context here, in order to improve quality or specificity of its own + * searches. This data will be returned with SEARCH intent(s). Null if + * no extra data is required. + * + * @see #startSearch + */ + public void triggerSearch(String query, + ComponentName launchActivity, + Bundle appSearchData) { + if (mIdent == 0) throw new IllegalArgumentException( + "Called from outside of an Activity context"); + if (!mAssociatedPackage.equals(launchActivity.getPackageName())) { + throw new IllegalArgumentException("invoking app search on a different package " + + "not associated with this search manager"); + } + if (query == null || TextUtils.getTrimmedLength(query) == 0) { + Log.w(TAG, "triggerSearch called with empty query, ignoring."); + return; + } + try { + mService.triggerSearch(query, launchActivity, appSearchData, mSearchManagerCallback, + mIdent); + } catch (RemoteException ex) { + Log.e(TAG, "triggerSearch() failed.", ex); } } @@ -1836,6 +1926,7 @@ public class SearchManager /** * @deprecated This method is an obsolete internal implementation detail. Do not use. */ + @Deprecated public void onCancel(DialogInterface dialog) { throw new UnsupportedOperationException(); } @@ -1843,6 +1934,7 @@ public class SearchManager /** * @deprecated This method is an obsolete internal implementation detail. Do not use. */ + @Deprecated public void onDismiss(DialogInterface dialog) { throw new UnsupportedOperationException(); } @@ -1889,6 +1981,21 @@ public class SearchManager * @hide because SearchableInfo is not part of the API. */ public Cursor getSuggestions(SearchableInfo searchable, String query) { + return getSuggestions(searchable, query, -1); + } + + /** + * Gets a cursor with search suggestions. + * + * @param searchable Information about how to get the suggestions. + * @param query The search text entered (so far). + * @param limit The query limit to pass to the suggestion provider. This is advisory, + * the returned cursor may contain more rows. Pass {@code -1} for no limit. + * @return a cursor with suggestions, or <code>null</null> the suggestion query failed. + * + * @hide because SearchableInfo is not part of the API. + */ + public Cursor getSuggestions(SearchableInfo searchable, String query, int limit) { if (searchable == null) { return null; } @@ -1900,7 +2007,9 @@ public class SearchManager Uri.Builder uriBuilder = new Uri.Builder() .scheme(ContentResolver.SCHEME_CONTENT) - .authority(authority); + .authority(authority) + .query("") // TODO: Remove, workaround for a bug in Uri.writeToParcel() + .fragment(""); // TODO: Remove, workaround for a bug in Uri.writeToParcel() // if content path provided, insert it now final String contentPath = searchable.getSuggestPath(); @@ -1908,7 +2017,7 @@ public class SearchManager uriBuilder.appendEncodedPath(contentPath); } - // append standard suggestion query path + // append standard suggestion query path uriBuilder.appendPath(SearchManager.SUGGEST_URI_PATH_QUERY); // get the query selection, may be null @@ -1921,10 +2030,11 @@ public class SearchManager uriBuilder.appendPath(query); } - Uri uri = uriBuilder - .query("") // TODO: Remove, workaround for a bug in Uri.writeToParcel() - .fragment("") // TODO: Remove, workaround for a bug in Uri.writeToParcel() - .build(); + if (limit > 0) { + uriBuilder.appendQueryParameter(SUGGEST_PARAMETER_LIMIT, String.valueOf(limit)); + } + + Uri uri = uriBuilder.build(); // finally, make the query return mContext.getContentResolver().query(uri, null, selection, selArgs, null); @@ -2000,4 +2110,4 @@ public class SearchManager Thread thread = Thread.currentThread(); Log.d(TAG, msg + " (" + thread.getName() + "-" + thread.getId() + ")"); } -} +}
\ No newline at end of file diff --git a/core/java/android/app/Service.java b/core/java/android/app/Service.java index d2fb605..30e1712 100644 --- a/core/java/android/app/Service.java +++ b/core/java/android/app/Service.java @@ -22,8 +22,10 @@ import android.content.Intent; import android.content.ContextWrapper; import android.content.Context; import android.content.res.Configuration; +import android.os.Build; import android.os.RemoteException; import android.os.IBinder; +import android.util.Log; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -62,18 +64,28 @@ import java.io.PrintWriter; * <p>There are two reasons that a service can be run by the system. If someone * calls {@link android.content.Context#startService Context.startService()} then the system will * retrieve the service (creating it and calling its {@link #onCreate} method - * if needed) and then call its {@link #onStart} method with the + * if needed) and then call its {@link #onStartCommand} method with the * arguments supplied by the client. The service will at this point continue * running until {@link android.content.Context#stopService Context.stopService()} or * {@link #stopSelf()} is called. Note that multiple calls to * Context.startService() do not nest (though they do result in multiple corresponding - * calls to onStart()), so no matter how many times it is started a service - * will be stopped once Context.stopService() or stopSelf() is called. + * calls to onStartCommand()), so no matter how many times it is started a service + * will be stopped once Context.stopService() or stopSelf() is called; however, + * services can use their {@link #stopSelf(int)} method to ensure the service is + * not stopped until started intents have been processed. + * + * <p>For started services, there are two additional major modes of operation + * they can decide to run in, depending on the value they return from + * onStartCommand(): {@link #START_STICKY} is used for services that are + * explicitly started and stopped as needed, while {@link #START_NOT_STICKY} + * or {@link #START_REDELIVER_INTENT} are used for services that should only + * remain running while processing any commands sent to them. See the linked + * documentation for more detail on the semantics. * * <p>Clients can also use {@link android.content.Context#bindService Context.bindService()} to * obtain a persistent connection to a service. This likewise creates the * service if it is not already running (calling {@link #onCreate} while - * doing so), but does not call onStart(). The client will receive the + * doing so), but does not call onStartCommand(). The client will receive the * {@link android.os.IBinder} object that the service returns from its * {@link #onBind} method, allowing the client to then make calls back * to the service. The service will remain running as long as the connection @@ -120,7 +132,7 @@ import java.io.PrintWriter; * * <ul> * <li><p>If the service is currently executing code in its - * {@link #onCreate onCreate()}, {@link #onStart onStart()}, + * {@link #onCreate onCreate()}, {@link #onStartCommand onStartCommand()}, * or {@link #onDestroy onDestroy()} methods, then the hosting process will * be a foreground process to ensure this code can execute without * being killed. @@ -133,16 +145,22 @@ import java.io.PrintWriter; * process is never less important than the most important client. That is, * if one of its clients is visible to the user, then the service itself is * considered to be visible. + * <li><p>A started service can use the {@link #startForeground(int, Notification)} + * API to put the service in a foreground state, where the system considers + * it to be something the user is actively aware of and thus not a candidate + * for killing when low on memory. (It is still theoretically possible for + * the service to be killed under extreme memory pressure from the current + * foreground application, but in practice this should not be a concern.) * </ul> * * <p>Note this means that most of the time your service is running, it may * be killed by the system if it is under heavy memory pressure. If this * happens, the system will later try to restart the service. An important - * consequence of this is that if you implement {@link #onStart onStart()} + * consequence of this is that if you implement {@link #onStartCommand onStartCommand()} * to schedule work to be done asynchronously or in another thread, then you - * may want to write information about that work into persistent storage - * during the onStart() call so that it does not get lost if the service later - * gets killed. + * may want to use {@link #START_FLAG_REDELIVERY} to have the system + * re-deliver an Intent for you so that it does not get lost if your service + * is killed while processing it. * * <p>Other application components running in the same process as the service * (such as an {@link android.app.Activity}) can, of course, increase the @@ -168,20 +186,127 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac } /** + * @deprecated Implement {@link #onStartCommand(Intent, int, int)} instead. + */ + @Deprecated + public void onStart(Intent intent, int startId) { + } + + /** + * Bits returned by {@link #onStartCommand} describing how to continue + * the service if it is killed. May be {@link #START_STICKY}, + * {@link #START_NOT_STICKY}, {@link #START_REDELIVER_INTENT}, + * or {@link #START_STICKY_COMPATIBILITY}. + */ + public static final int START_CONTINUATION_MASK = 0xf; + + /** + * Constant to return from {@link #onStartCommand}: compatibility + * version of {@link #START_STICKY} that does not guarantee that + * {@link #onStartCommand} will be called again after being killed. + */ + public static final int START_STICKY_COMPATIBILITY = 0; + + /** + * Constant to return from {@link #onStartCommand}: if this service's + * process is killed while it is started (after returning from + * {@link #onStartCommand}), then leave it in the started state but + * don't retain this delivered intent. Later the system will try to + * re-create the service. Because it is in the started state, it will + * guarantee to call {@link #onStartCommand} after creating the new + * service instance; if there are not any pending start commands to be + * delivered to the service, it will be called with a null intent + * object, so you must take care to check for this. + * + * <p>This mode makes sense for things that will be explicitly started + * and stopped to run for arbitrary periods of time, such as a service + * performing background music playback. + */ + public static final int START_STICKY = 1; + + /** + * Constant to return from {@link #onStartCommand}: if this service's + * process is killed while it is started (after returning from + * {@link #onStartCommand}), and there are no new start intents to + * deliver to it, then take the service out of the started state and + * don't recreate until a future explicit call to + * {@link Context#startService Context.startService(Intent)}. The + * service will not receive a {@link #onStartCommand(Intent, int, int)} + * call with a null Intent because it will not be re-started if there + * are no pending Intents to deliver. + * + * <p>This mode makes sense for things that want to do some work as a + * result of being started, but can be stopped when under memory pressure + * and will explicit start themselves again later to do more work. An + * example of such a service would be one that polls for data from + * a server: it could schedule an alarm to poll every N minutes by having + * the alarm start its service. When its {@link #onStartCommand} is + * called from the alarm, it schedules a new alarm for N minutes later, + * and spawns a thread to do its networking. If its process is killed + * while doing that check, the service will not be restarted until the + * alarm goes off. + */ + public static final int START_NOT_STICKY = 2; + + /** + * Constant to return from {@link #onStartCommand}: if this service's + * process is killed while it is started (after returning from + * {@link #onStartCommand}), then it will be scheduled for a restart + * and the last delivered Intent re-delivered to it again via + * {@link #onStartCommand}. This Intent will remain scheduled for + * redelivery until the service calls {@link #stopSelf(int)} with the + * start ID provided to {@link #onStartCommand}. The + * service will not receive a {@link #onStartCommand(Intent, int, int)} + * call with a null Intent because it will will only be re-started if + * it is not finished processing all Intents sent to it (and any such + * pending events will be delivered at the point of restart). + */ + public static final int START_REDELIVER_INTENT = 3; + + /** + * This flag is set in {@link #onStartCommand} if the Intent is a + * re-delivery of a previously delivered intent, because the service + * had previously returned {@link #START_REDELIVER_INTENT} but had been + * killed before calling {@link #stopSelf(int)} for that Intent. + */ + public static final int START_FLAG_REDELIVERY = 0x0001; + + /** + * This flag is set in {@link #onStartCommand} if the Intent is a + * a retry because the original attempt never got to or returned from + * {@link #onStartCommand(Intent, int, int)}. + */ + public static final int START_FLAG_RETRY = 0x0002; + + /** * Called by the system every time a client explicitly starts the service by calling * {@link android.content.Context#startService}, providing the arguments it supplied and a * unique integer token representing the start request. Do not call this method directly. - * + * + * <p>For backwards compatibility, the default implementation calls + * {@link #onStart} and returns either {@link #START_STICKY} + * or {@link #START_STICKY_COMPATIBILITY}. + * * @param intent The Intent supplied to {@link android.content.Context#startService}, - * as given. + * as given. This may be null if the service is being restarted after + * its process has gone away, and it had previously returned anything + * except {@link #START_STICKY_COMPATIBILITY}. + * @param flags Additional data about this start request. Currently either + * 0, {@link #START_FLAG_REDELIVERY}, or {@link #START_FLAG_RETRY}. * @param startId A unique integer representing this specific request to - * start. Use with {@link #stopSelfResult(int)}. + * start. Use with {@link #stopSelfResult(int)}. + * + * @return The return value indicates what semantics the system should + * use for the service's current started state. It may be one of the + * constants associated with the {@link #START_CONTINUATION_MASK} bits. * * @see #stopSelfResult(int) */ - public void onStart(Intent intent, int startId) { + public int onStartCommand(Intent intent, int flags, int startId) { + onStart(intent, startId); + return mStartCompatibility ? START_STICKY_COMPATIBILITY : START_STICKY; } - + /** * Called by the system to notify a Service that it is no longer used and is being removed. The * service should clean up an resources it holds (threads, registered @@ -284,6 +409,13 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac * safely avoid stopping if there is a start request from a client that you * haven't yet seen in {@link #onStart}. * + * <p><em>Be careful about ordering of your calls to this function.</em>. + * If you call this function with the most-recently received ID before + * you have called it for previously received IDs, the service will be + * immediately stopped anyway. If you may end up processing IDs out + * of order (such as by dispatching them on separate threads), then you + * are responsible for stopping them in the same order you received them.</p> + * * @param startId The most recent start identifier received in {@link * #onStart}. * @return Returns true if the startId matches the last start request @@ -304,24 +436,61 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac } /** - * Control whether this service is considered to be a foreground service. + * @deprecated This is a now a no-op, use + * {@link #startForeground(int, Notification)} instead. This method + * has been turned into a no-op rather than simply being deprecated + * because analysis of numerous poorly behaving devices has shown that + * increasingly often the trouble is being caused in part by applications + * that are abusing it. Thus, given a choice between introducing + * problems in existing applications using this API (by allowing them to + * be killed when they would like to avoid it), vs allowing the performance + * of the entire system to be decreased, this method was deemed less + * important. + */ + @Deprecated + public final void setForeground(boolean isForeground) { + Log.w(TAG, "setForeground: ignoring old API call on " + getClass().getName()); + } + + /** + * Make this service run in the foreground, supplying the ongoing + * notification to be shown to the user while in this state. * By default services are background, meaning that if the system needs to * kill them to reclaim more memory (such as to display a large page in a * web browser), they can be killed without too much harm. You can set this - * flag if killing your service would be disruptive to the user: such as + * flag if killing your service would be disruptive to the user, such as * if your service is performing background music playback, so the user * would notice if their music stopped playing. * - * @param isForeground Determines whether this service is considered to - * be foreground (true) or background (false). + * @param id The identifier for this notification as per + * {@link NotificationManager#notify(int, Notification) + * NotificationManager.notify(int, Notification)}. + * @param notification The Notification to be displayed. + * + * @see #stopForeground(boolean) */ - public final void setForeground(boolean isForeground) { - if (mActivityManager == null) { - return; + public final void startForeground(int id, Notification notification) { + try { + mActivityManager.setServiceForeground( + new ComponentName(this, mClassName), mToken, id, + notification, true); + } catch (RemoteException ex) { } + } + + /** + * Remove this service from foreground state, allowing it to be killed if + * more memory is needed. + * @param removeNotification If true, the notification previously provided + * to {@link #startForeground} will be removed. Otherwise it will remain + * until a later call removes it (or the service is destroyed). + * @see #startForeground(int, Notification) + */ + public final void stopForeground(boolean removeNotification) { try { mActivityManager.setServiceForeground( - new ComponentName(this, mClassName), mToken, isForeground); + new ComponentName(this, mClassName), mToken, 0, null, + removeNotification); } catch (RemoteException ex) { } } @@ -363,6 +532,8 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac mToken = token; mApplication = application; mActivityManager = (IActivityManager)activityManager; + mStartCompatibility = getApplicationInfo().targetSdkVersion + < Build.VERSION_CODES.ECLAIR; } final String getClassName() { @@ -375,4 +546,5 @@ public abstract class Service extends ContextWrapper implements ComponentCallbac private IBinder mToken = null; private Application mApplication = null; private IActivityManager mActivityManager = null; + private boolean mStartCompatibility = false; } diff --git a/core/java/android/app/SuggestionsAdapter.java b/core/java/android/app/SuggestionsAdapter.java index 90f8c50..12be97c 100644 --- a/core/java/android/app/SuggestionsAdapter.java +++ b/core/java/android/app/SuggestionsAdapter.java @@ -20,6 +20,7 @@ import android.app.SearchManager.DialogCursorProtocol; import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; +import android.content.ContentResolver.OpenResourceIdResult; import android.content.pm.ActivityInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; @@ -27,7 +28,6 @@ import android.content.res.Resources; import android.database.Cursor; import android.graphics.drawable.ColorDrawable; import android.graphics.drawable.Drawable; -import android.graphics.drawable.DrawableContainer; import android.graphics.drawable.StateListDrawable; import android.net.Uri; import android.os.Bundle; @@ -36,12 +36,13 @@ import android.text.Html; import android.text.TextUtils; import android.util.Log; import android.util.SparseArray; +import android.view.KeyEvent; import android.view.View; import android.view.ViewGroup; +import android.widget.Filter; import android.widget.ImageView; import android.widget.ResourceCursorAdapter; import android.widget.TextView; -import android.widget.Filter; import java.io.FileNotFoundException; import java.io.IOException; @@ -57,6 +58,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter { private static final boolean DBG = false; private static final String LOG_TAG = "SuggestionsAdapter"; + private static final int QUERY_LIMIT = 50; private SearchManager mSearchManager; private SearchDialog mSearchDialog; @@ -185,7 +187,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter { mSearchDialog.getWindow().getDecorView().post(mStartSpinnerRunnable); } try { - final Cursor cursor = mSearchManager.getSuggestions(mSearchable, query); + final Cursor cursor = mSearchManager.getSuggestions(mSearchable, query, QUERY_LIMIT); // trigger fill window so the spinner stays up until the results are copied over and // closer to being ready if (!mGlobalSearchMode && cursor != null) cursor.getCount(); @@ -288,12 +290,16 @@ class SuggestionsAdapter extends ResourceCursorAdapter { * @param cursor The cursor * @param position The position that was clicked. */ - void callCursorOnClick(Cursor cursor, int position) { + void callCursorOnClick(Cursor cursor, int position, int actionKey, String actionMsg) { if (!mGlobalSearchMode) return; - final Bundle request = new Bundle(1); + final Bundle request = new Bundle(5); request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.CLICK); request.putInt(DialogCursorProtocol.CLICK_SEND_POSITION, position); request.putInt(DialogCursorProtocol.CLICK_SEND_MAX_DISPLAY_POS, mMaxDisplayed); + if (actionKey != KeyEvent.KEYCODE_UNKNOWN) { + request.putInt(DialogCursorProtocol.CLICK_SEND_ACTION_KEY, actionKey); + request.putString(DialogCursorProtocol.CLICK_SEND_ACTION_MSG, actionMsg); + } final Bundle response = cursor.respond(request); mMaxDisplayed = -1; mListItemToSelect = response.getInt( @@ -301,6 +307,23 @@ class SuggestionsAdapter extends ResourceCursorAdapter { } /** + * Tell the cursor that a search was started without using a suggestion. + * + * @param query The search query. + */ + void reportSearch(String query) { + if (!mGlobalSearchMode) return; + Cursor cursor = getCursor(); + if (cursor == null) return; + final Bundle request = new Bundle(3); + request.putInt(DialogCursorProtocol.METHOD, DialogCursorProtocol.SEARCH); + request.putString(DialogCursorProtocol.SEARCH_SEND_QUERY, query); + request.putInt(DialogCursorProtocol.SEARCH_SEND_MAX_DISPLAY_POS, mMaxDisplayed); + // the response is always empty + cursor.respond(request); + } + + /** * Tags the view with cached child view look-ups. */ @Override @@ -378,7 +401,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter { Drawable.ConstantState cachedBg = mBackgroundsCache.get(backgroundColor); if (cachedBg != null) { if (DBG) Log.d(LOG_TAG, "Background cache hit for color " + backgroundColor); - return cachedBg.newDrawable(); + return cachedBg.newDrawable(mProviderContext.getResources()); } if (DBG) Log.d(LOG_TAG, "Creating new background for color " + backgroundColor); ColorDrawable transparent = new ColorDrawable(0); @@ -550,54 +573,91 @@ class SuggestionsAdapter extends ResourceCursorAdapter { if (drawableId == null || drawableId.length() == 0 || "0".equals(drawableId)) { return null; } - - // First, check the cache. - Drawable.ConstantState cached = mOutsideDrawablesCache.get(drawableId); - if (cached != null) { - if (DBG) Log.d(LOG_TAG, "Found icon in cache: " + drawableId); - return cached.newDrawable(); - } - - Drawable drawable = null; try { - // Not cached, try using it as a plain resource ID in the provider's context. + // First, see if it's just an integer int resourceId = Integer.parseInt(drawableId); + // It's an int, look for it in the cache + String drawableUri = ContentResolver.SCHEME_ANDROID_RESOURCE + + "://" + mProviderContext.getPackageName() + "/" + resourceId; + // Must use URI as cache key, since ints are app-specific + Drawable drawable = checkIconCache(drawableUri); + if (drawable != null) { + return drawable; + } + // Not cached, find it by resource ID drawable = mProviderContext.getResources().getDrawable(resourceId); - if (DBG) Log.d(LOG_TAG, "Found icon by resource ID: " + drawableId); + // Stick it in the cache, using the URI as key + storeInIconCache(drawableUri, drawable); + return drawable; } catch (NumberFormatException nfe) { - // The id was not an integer resource id. - // Let the ContentResolver handle content, android.resource and file URIs. - try { - Uri uri = Uri.parse(drawableId); + // It's not an integer, use it as a URI + Drawable drawable = checkIconCache(drawableId); + if (drawable != null) { + return drawable; + } + Uri uri = Uri.parse(drawableId); + drawable = getDrawable(uri); + storeInIconCache(drawableId, drawable); + return drawable; + } catch (Resources.NotFoundException nfe) { + // It was an integer, but it couldn't be found, bail out + Log.w(LOG_TAG, "Icon resource not found: " + drawableId); + return null; + } + } + + /** + * Gets a drawable by URI, without using the cache. + * + * @return A drawable, or {@code null} if the drawable could not be loaded. + */ + private Drawable getDrawable(Uri uri) { + try { + String scheme = uri.getScheme(); + if (ContentResolver.SCHEME_ANDROID_RESOURCE.equals(scheme)) { + // Load drawables through Resources, to get the source density information + OpenResourceIdResult r = + mProviderContext.getContentResolver().getResourceId(uri); + try { + return r.r.getDrawable(r.id); + } catch (Resources.NotFoundException ex) { + throw new FileNotFoundException("Resource does not exist: " + uri); + } + } else { + // Let the ContentResolver handle content and file URIs. InputStream stream = mProviderContext.getContentResolver().openInputStream(uri); - if (stream != null) { + if (stream == null) { + throw new FileNotFoundException("Failed to open " + uri); + } + try { + return Drawable.createFromStream(stream, null); + } finally { try { - drawable = Drawable.createFromStream(stream, null); - } finally { - try { - stream.close(); - } catch (IOException ex) { - Log.e(LOG_TAG, "Error closing icon stream for " + uri, ex); - } + stream.close(); + } catch (IOException ex) { + Log.e(LOG_TAG, "Error closing icon stream for " + uri, ex); } } - if (DBG) Log.d(LOG_TAG, "Opened icon input stream: " + drawableId); - } catch (FileNotFoundException fnfe) { - if (DBG) Log.d(LOG_TAG, "Icon stream not found: " + drawableId); - // drawable = null; } + } catch (FileNotFoundException fnfe) { + Log.w(LOG_TAG, "Icon not found: " + uri + ", " + fnfe.getMessage()); + return null; + } + } - // 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.getConstantState()); - } - } catch (Resources.NotFoundException nfe) { - if (DBG) Log.d(LOG_TAG, "Icon resource not found: " + drawableId); - // drawable = null; + private Drawable checkIconCache(String resourceUri) { + Drawable.ConstantState cached = mOutsideDrawablesCache.get(resourceUri); + if (cached == null) { + return null; } + if (DBG) Log.d(LOG_TAG, "Found icon in cache: " + resourceUri); + return cached.newDrawable(); + } - return drawable; + private void storeInIconCache(String resourceUri, Drawable drawable) { + if (drawable != null) { + mOutsideDrawablesCache.put(resourceUri, drawable.getConstantState()); + } } /** @@ -646,7 +706,7 @@ class SuggestionsAdapter extends ResourceCursorAdapter { // Using containsKey() since we also store null values. if (mOutsideDrawablesCache.containsKey(componentIconKey)) { Drawable.ConstantState cached = mOutsideDrawablesCache.get(componentIconKey); - return cached == null ? null : cached.newDrawable(); + return cached == null ? null : cached.newDrawable(mProviderContext.getResources()); } // Then try the activity or application icon Drawable drawable = getActivityIcon(component); diff --git a/core/java/android/app/WallpaperInfo.aidl b/core/java/android/app/WallpaperInfo.aidl new file mode 100644 index 0000000..7bbdcae --- /dev/null +++ b/core/java/android/app/WallpaperInfo.aidl @@ -0,0 +1,19 @@ +/* +** 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; + +parcelable WallpaperInfo; diff --git a/core/java/android/app/WallpaperInfo.java b/core/java/android/app/WallpaperInfo.java new file mode 100644 index 0000000..34d3133 --- /dev/null +++ b/core/java/android/app/WallpaperInfo.java @@ -0,0 +1,277 @@ +package android.app; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; + +import android.content.ComponentName; +import android.content.Context; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.content.res.Resources.NotFoundException; +import android.content.res.TypedArray; +import android.content.res.XmlResourceParser; +import android.graphics.drawable.Drawable; +import android.os.Parcel; +import android.os.Parcelable; +import android.service.wallpaper.WallpaperService; +import android.util.AttributeSet; +import android.util.Printer; +import android.util.Xml; + +import java.io.IOException; + +/** + * This class is used to specify meta information of a wallpaper service. + */ +public final class WallpaperInfo implements Parcelable { + static final String TAG = "WallpaperInfo"; + + /** + * The Service that implements this wallpaper component. + */ + final ResolveInfo mService; + + /** + * The wallpaper setting activity's name, to + * launch the setting activity of this wallpaper. + */ + final String mSettingsActivityName; + + /** + * Resource identifier for this wallpaper's thumbnail image. + */ + final int mThumbnailResource; + + /** + * Resource identifier for a string indicating the author of the wallpaper. + */ + final int mAuthorResource; + + /** + * Resource identifier for a string containing a short description of the wallpaper. + */ + final int mDescriptionResource; + + /** + * Constructor. + * + * @param context The Context in which we are parsing the wallpaper. + * @param service The ResolveInfo returned from the package manager about + * this wallpaper's component. + */ + public WallpaperInfo(Context context, ResolveInfo service) + throws XmlPullParserException, IOException { + mService = service; + ServiceInfo si = service.serviceInfo; + + PackageManager pm = context.getPackageManager(); + String settingsActivityComponent = null; + int thumbnailRes = -1; + int authorRes = -1; + int descriptionRes = -1; + + XmlResourceParser parser = null; + try { + parser = si.loadXmlMetaData(pm, WallpaperService.SERVICE_META_DATA); + if (parser == null) { + throw new XmlPullParserException("No " + + WallpaperService.SERVICE_META_DATA + " meta-data"); + } + + AttributeSet attrs = Xml.asAttributeSet(parser); + + int type; + while ((type=parser.next()) != XmlPullParser.END_DOCUMENT + && type != XmlPullParser.START_TAG) { + } + + String nodeName = parser.getName(); + if (!"wallpaper".equals(nodeName)) { + throw new XmlPullParserException( + "Meta-data does not start with wallpaper tag"); + } + + TypedArray sa = context.getResources().obtainAttributes(attrs, + com.android.internal.R.styleable.Wallpaper); + settingsActivityComponent = sa.getString( + com.android.internal.R.styleable.Wallpaper_settingsActivity); + + thumbnailRes = sa.getResourceId( + com.android.internal.R.styleable.Wallpaper_thumbnail, + -1); + authorRes = sa.getResourceId( + com.android.internal.R.styleable.Wallpaper_wallpaperAuthor, + -1); + descriptionRes = sa.getResourceId( + com.android.internal.R.styleable.Wallpaper_wallpaperDescription, + -1); + + sa.recycle(); + } finally { + if (parser != null) parser.close(); + } + + mSettingsActivityName = settingsActivityComponent; + mThumbnailResource = thumbnailRes; + mAuthorResource = authorRes; + mDescriptionResource = descriptionRes; + } + + WallpaperInfo(Parcel source) { + mSettingsActivityName = source.readString(); + mThumbnailResource = source.readInt(); + mAuthorResource = source.readInt(); + mDescriptionResource = source.readInt(); + mService = ResolveInfo.CREATOR.createFromParcel(source); + } + + /** + * Return the .apk package that implements this wallpaper. + */ + public String getPackageName() { + return mService.serviceInfo.packageName; + } + + /** + * Return the class name of the service component that implements + * this wallpaper. + */ + public String getServiceName() { + return mService.serviceInfo.name; + } + + /** + * Return the raw information about the Service implementing this + * wallpaper. Do not modify the returned object. + */ + public ServiceInfo getServiceInfo() { + return mService.serviceInfo; + } + + /** + * Return the component of the service that implements this wallpaper. + */ + public ComponentName getComponent() { + return new ComponentName(mService.serviceInfo.packageName, + mService.serviceInfo.name); + } + + /** + * Load the user-displayed label for this wallpaper. + * + * @param pm Supply a PackageManager used to load the wallpaper's + * resources. + */ + public CharSequence loadLabel(PackageManager pm) { + return mService.loadLabel(pm); + } + + /** + * Load the user-displayed icon for this wallpaper. + * + * @param pm Supply a PackageManager used to load the wallpaper's + * resources. + */ + public Drawable loadIcon(PackageManager pm) { + return mService.loadIcon(pm); + } + + /** + * Load the thumbnail image for this wallpaper. + * + * @param pm Supply a PackageManager used to load the wallpaper's + * resources. + */ + public Drawable loadThumbnail(PackageManager pm) { + if (mThumbnailResource < 0) return null; + + return pm.getDrawable(mService.serviceInfo.packageName, + mThumbnailResource, + null); + } + + /** + * Return a string indicating the author(s) of this wallpaper. + */ + public CharSequence loadAuthor(PackageManager pm) throws NotFoundException { + if (mAuthorResource <= 0) throw new NotFoundException(); + return pm.getText( + (mService.resolvePackageName != null) + ? mService.resolvePackageName + : getPackageName(), + mAuthorResource, + null); + } + + /** + * Return a brief summary of this wallpaper's behavior. + */ + public CharSequence loadDescription(PackageManager pm) throws NotFoundException { + if (mDescriptionResource <= 0) throw new NotFoundException(); + return pm.getText( + (mService.resolvePackageName != null) + ? mService.resolvePackageName + : getPackageName(), + mDescriptionResource, + null); + } + + /** + * Return the class name of an activity that provides a settings UI for + * the wallpaper. You can launch this activity be starting it with + * an {@link android.content.Intent} whose action is MAIN and with an + * explicit {@link android.content.ComponentName} + * composed of {@link #getPackageName} and the class name returned here. + * + * <p>A null will be returned if there is no settings activity associated + * with the wallpaper. + */ + public String getSettingsActivity() { + return mSettingsActivityName; + } + + public void dump(Printer pw, String prefix) { + pw.println(prefix + "Service:"); + mService.dump(pw, prefix + " "); + pw.println(prefix + "mSettingsActivityName=" + mSettingsActivityName); + } + + @Override + public String toString() { + return "WallpaperInfo{" + mService.serviceInfo.name + + ", settings: " + + mSettingsActivityName + "}"; + } + + /** + * Used to package this object into a {@link Parcel}. + * + * @param dest The {@link Parcel} to be written. + * @param flags The flags used for parceling. + */ + public void writeToParcel(Parcel dest, int flags) { + dest.writeString(mSettingsActivityName); + dest.writeInt(mThumbnailResource); + dest.writeInt(mAuthorResource); + dest.writeInt(mDescriptionResource); + mService.writeToParcel(dest, flags); + } + + /** + * Used to make this class parcelable. + */ + public static final Parcelable.Creator<WallpaperInfo> CREATOR = new Parcelable.Creator<WallpaperInfo>() { + public WallpaperInfo createFromParcel(Parcel source) { + return new WallpaperInfo(source); + } + + public WallpaperInfo[] newArray(int size) { + return new WallpaperInfo[size]; + } + }; + + public int describeContents() { + return 0; + } +} diff --git a/core/java/android/app/WallpaperManager.java b/core/java/android/app/WallpaperManager.java new file mode 100644 index 0000000..e98b286 --- /dev/null +++ b/core/java/android/app/WallpaperManager.java @@ -0,0 +1,709 @@ +/* + * 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.app; + +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.ColorFilter; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.ViewRoot; + +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +/** + * Provides access to the system wallpaper. With WallpaperManager, you can + * get the current wallpaper, get the desired dimensions for the wallpaper, set + * the wallpaper, and more. Get an instance of WallpaperManager with + * {@link #getInstance(android.content.Context) getInstance()}. + */ +public class WallpaperManager { + private static String TAG = "WallpaperManager"; + private static boolean DEBUG = false; + private float mWallpaperXStep = -1; + private float mWallpaperYStep = -1; + + /** + * Launch an activity for the user to pick the current global live + * wallpaper. + */ + public static final String ACTION_LIVE_WALLPAPER_CHOOSER + = "android.service.wallpaper.LIVE_WALLPAPER_CHOOSER"; + + private final Context mContext; + + /** + * Special drawable that draws a wallpaper as fast as possible. Assumes + * no scaling or placement off (0,0) of the wallpaper (this should be done + * at the time the bitmap is loaded). + */ + static class FastBitmapDrawable extends Drawable { + private final Bitmap mBitmap; + private final int mWidth; + private final int mHeight; + private int mDrawLeft; + private int mDrawTop; + + private FastBitmapDrawable(Bitmap bitmap) { + mBitmap = bitmap; + mWidth = bitmap.getWidth(); + mHeight = bitmap.getHeight(); + setBounds(0, 0, mWidth, mHeight); + } + + @Override + public void draw(Canvas canvas) { + canvas.drawBitmap(mBitmap, mDrawLeft, mDrawTop, null); + } + + @Override + public int getOpacity() { + return PixelFormat.OPAQUE; + } + + @Override + public void setBounds(int left, int top, int right, int bottom) { + mDrawLeft = left + (right-left - mWidth) / 2; + mDrawTop = top + (bottom-top - mHeight) / 2; + } + + @Override + public void setBounds(Rect bounds) { + // TODO Auto-generated method stub + super.setBounds(bounds); + } + + @Override + public void setAlpha(int alpha) { + throw new UnsupportedOperationException( + "Not supported with this drawable"); + } + + @Override + public void setColorFilter(ColorFilter cf) { + throw new UnsupportedOperationException( + "Not supported with this drawable"); + } + + @Override + public void setDither(boolean dither) { + throw new UnsupportedOperationException( + "Not supported with this drawable"); + } + + @Override + public void setFilterBitmap(boolean filter) { + throw new UnsupportedOperationException( + "Not supported with this drawable"); + } + + @Override + public int getIntrinsicWidth() { + return mWidth; + } + + @Override + public int getIntrinsicHeight() { + return mHeight; + } + + @Override + public int getMinimumWidth() { + return mWidth; + } + + @Override + public int getMinimumHeight() { + return mHeight; + } + } + + static class Globals extends IWallpaperManagerCallback.Stub { + private IWallpaperManager mService; + private Bitmap mWallpaper; + private Bitmap mDefaultWallpaper; + + private static final int MSG_CLEAR_WALLPAPER = 1; + + private final Handler mHandler; + + Globals(Looper looper) { + IBinder b = ServiceManager.getService(Context.WALLPAPER_SERVICE); + mService = IWallpaperManager.Stub.asInterface(b); + mHandler = new Handler(looper) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_CLEAR_WALLPAPER: + synchronized (this) { + mWallpaper = null; + mDefaultWallpaper = null; + } + break; + } + } + }; + } + + public void onWallpaperChanged() { + /* The wallpaper has changed but we shouldn't eagerly load the + * wallpaper as that would be inefficient. Reset the cached wallpaper + * to null so if the user requests the wallpaper again then we'll + * fetch it. + */ + mHandler.sendEmptyMessage(MSG_CLEAR_WALLPAPER); + } + + public Bitmap peekWallpaperBitmap(Context context, boolean returnDefault) { + synchronized (this) { + if (mWallpaper != null) { + return mWallpaper; + } + if (mDefaultWallpaper != null) { + return mDefaultWallpaper; + } + mWallpaper = getCurrentWallpaperLocked(context); + if (mWallpaper == null && returnDefault) { + mDefaultWallpaper = getDefaultWallpaperLocked(context); + return mDefaultWallpaper; + } + return mWallpaper; + } + } + + private Bitmap getCurrentWallpaperLocked(Context context) { + try { + Bundle params = new Bundle(); + ParcelFileDescriptor fd = mService.getWallpaper(this, params); + if (fd != null) { + int width = params.getInt("width", 0); + int height = params.getInt("height", 0); + + if (width <= 0 || height <= 0) { + // Degenerate case: no size requested, just load + // bitmap as-is. + Bitmap bm = BitmapFactory.decodeFileDescriptor( + fd.getFileDescriptor(), null, null); + try { + fd.close(); + } catch (IOException e) { + } + if (bm != null) { + bm.setDensity(DisplayMetrics.DENSITY_DEVICE); + } + return bm; + } + + // Load the bitmap with full color depth, to preserve + // quality for later processing. + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inDither = false; + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + Bitmap bm = BitmapFactory.decodeFileDescriptor( + fd.getFileDescriptor(), null, options); + try { + fd.close(); + } catch (IOException e) { + } + + return generateBitmap(context, bm, width, height); + } + } catch (RemoteException e) { + } + return null; + } + + private Bitmap getDefaultWallpaperLocked(Context context) { + try { + InputStream is = context.getResources().openRawResource( + com.android.internal.R.drawable.default_wallpaper); + if (is != null) { + int width = mService.getWidthHint(); + int height = mService.getHeightHint(); + + if (width <= 0 || height <= 0) { + // Degenerate case: no size requested, just load + // bitmap as-is. + Bitmap bm = BitmapFactory.decodeStream(is, null, null); + try { + is.close(); + } catch (IOException e) { + } + if (bm != null) { + bm.setDensity(DisplayMetrics.DENSITY_DEVICE); + } + return bm; + } + + // Load the bitmap with full color depth, to preserve + // quality for later processing. + BitmapFactory.Options options = new BitmapFactory.Options(); + options.inDither = false; + options.inPreferredConfig = Bitmap.Config.ARGB_8888; + Bitmap bm = BitmapFactory.decodeStream(is, null, options); + try { + is.close(); + } catch (IOException e) { + } + + return generateBitmap(context, bm, width, height); + } + } catch (RemoteException e) { + } + return null; + } + } + + private static Object mSync = new Object(); + private static Globals sGlobals; + + static void initGlobals(Looper looper) { + synchronized (mSync) { + if (sGlobals == null) { + sGlobals = new Globals(looper); + } + } + } + + /*package*/ WallpaperManager(Context context, Handler handler) { + mContext = context; + initGlobals(context.getMainLooper()); + } + + /** + * Retrieve a WallpaperManager associated with the given Context. + */ + public static WallpaperManager getInstance(Context context) { + return (WallpaperManager)context.getSystemService( + Context.WALLPAPER_SERVICE); + } + + /** @hide */ + public IWallpaperManager getIWallpaperManager() { + return sGlobals.mService; + } + + /** + * Retrieve the current system wallpaper; if + * no wallpaper is set, the system default wallpaper is returned. + * This is returned as an + * abstract Drawable that you can install in a View to display whatever + * wallpaper the user has currently set. + * + * @return Returns a Drawable object that will draw the wallpaper. + */ + public Drawable getDrawable() { + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true); + if (bm != null) { + Drawable dr = new BitmapDrawable(mContext.getResources(), bm); + dr.setDither(false); + return dr; + } + return null; + } + + /** + * Retrieve the current system wallpaper; if there is no wallpaper set, + * a null pointer is returned. This is returned as an + * abstract Drawable that you can install in a View to display whatever + * wallpaper the user has currently set. + * + * @return Returns a Drawable object that will draw the wallpaper or a + * null pointer if these is none. + */ + public Drawable peekDrawable() { + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false); + if (bm != null) { + Drawable dr = new BitmapDrawable(mContext.getResources(), bm); + dr.setDither(false); + return dr; + } + return null; + } + + /** + * Like {@link #getDrawable()}, but the returned Drawable has a number + * of limitations to reduce its overhead as much as possible. It will + * never scale the wallpaper (only centering it if the requested bounds + * do match the bitmap bounds, which should not be typical), doesn't + * allow setting an alpha, color filter, or other attributes, etc. The + * bounds of the returned drawable will be initialized to the same bounds + * as the wallpaper, so normally you will not need to touch it. The + * drawable also assumes that it will be used in a context running in + * the same density as the screen (not in density compatibility mode). + * + * @return Returns a Drawable object that will draw the wallpaper. + */ + public Drawable getFastDrawable() { + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, true); + if (bm != null) { + Drawable dr = new FastBitmapDrawable(bm); + return dr; + } + return null; + } + + /** + * Like {@link #getFastDrawable()}, but if there is no wallpaper set, + * a null pointer is returned. + * + * @return Returns an optimized Drawable object that will draw the + * wallpaper or a null pointer if these is none. + */ + public Drawable peekFastDrawable() { + Bitmap bm = sGlobals.peekWallpaperBitmap(mContext, false); + if (bm != null) { + Drawable dr = new FastBitmapDrawable(bm); + return dr; + } + return null; + } + + /** + * If the current wallpaper is a live wallpaper component, return the + * information about that wallpaper. Otherwise, if it is a static image, + * simply return null. + */ + public WallpaperInfo getWallpaperInfo() { + try { + return sGlobals.mService.getWallpaperInfo(); + } catch (RemoteException e) { + return null; + } + } + + /** + * Change the current system wallpaper to the bitmap in the given resource. + * The resource is opened as a raw data stream and copied into the + * wallpaper; it must be a valid PNG or JPEG image. On success, the intent + * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. + * + * @param resid The bitmap to save. + * + * @throws IOException If an error occurs reverting to the default + * wallpaper. + */ + public void setResource(int resid) throws IOException { + try { + Resources resources = mContext.getResources(); + /* Set the wallpaper to the default values */ + ParcelFileDescriptor fd = sGlobals.mService.setWallpaper( + "res:" + resources.getResourceName(resid)); + if (fd != null) { + FileOutputStream fos = null; + try { + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + setWallpaper(resources.openRawResource(resid), fos); + } finally { + if (fos != null) { + fos.close(); + } + } + } + } catch (RemoteException e) { + } + } + + /** + * Change the current system wallpaper to a bitmap. The given bitmap is + * converted to a PNG and stored as the wallpaper. On success, the intent + * {@link Intent#ACTION_WALLPAPER_CHANGED} is broadcast. + * + * @param bitmap The bitmap to save. + * + * @throws IOException If an error occurs reverting to the default + * wallpaper. + */ + public void setBitmap(Bitmap bitmap) throws IOException { + try { + ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null); + if (fd == null) { + return; + } + FileOutputStream fos = null; + try { + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + bitmap.compress(Bitmap.CompressFormat.PNG, 90, fos); + } finally { + if (fos != null) { + fos.close(); + } + } + } catch (RemoteException e) { + } + } + + /** + * Change the current system wallpaper to a specific byte stream. The + * give InputStream is copied into persistent storage and will now be + * used as the wallpaper. Currently it must be either a JPEG or PNG + * image. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} + * is broadcast. + * + * @param data A stream containing the raw data to install as a wallpaper. + * + * @throws IOException If an error occurs reverting to the default + * wallpaper. + */ + public void setStream(InputStream data) throws IOException { + try { + ParcelFileDescriptor fd = sGlobals.mService.setWallpaper(null); + if (fd == null) { + return; + } + FileOutputStream fos = null; + try { + fos = new ParcelFileDescriptor.AutoCloseOutputStream(fd); + setWallpaper(data, fos); + } finally { + if (fos != null) { + fos.close(); + } + } + } catch (RemoteException e) { + } + } + + private void setWallpaper(InputStream data, FileOutputStream fos) + throws IOException { + byte[] buffer = new byte[32768]; + int amt; + while ((amt=data.read(buffer)) > 0) { + fos.write(buffer, 0, amt); + } + } + + /** + * Returns the desired minimum width for the wallpaper. Callers of + * {@link #setBitmap(android.graphics.Bitmap)} or + * {@link #setStream(java.io.InputStream)} should check this value + * beforehand to make sure the supplied wallpaper respects the desired + * minimum width. + * + * If the returned value is <= 0, the caller should use the width of + * the default display instead. + * + * @return The desired minimum width for the wallpaper. This value should + * be honored by applications that set the wallpaper but it is not + * mandatory. + */ + public int getDesiredMinimumWidth() { + try { + return sGlobals.mService.getWidthHint(); + } catch (RemoteException e) { + // Shouldn't happen! + return 0; + } + } + + /** + * Returns the desired minimum height for the wallpaper. Callers of + * {@link #setBitmap(android.graphics.Bitmap)} or + * {@link #setStream(java.io.InputStream)} should check this value + * beforehand to make sure the supplied wallpaper respects the desired + * minimum height. + * + * If the returned value is <= 0, the caller should use the height of + * the default display instead. + * + * @return The desired minimum height for the wallpaper. This value should + * be honored by applications that set the wallpaper but it is not + * mandatory. + */ + public int getDesiredMinimumHeight() { + try { + return sGlobals.mService.getHeightHint(); + } catch (RemoteException e) { + // Shouldn't happen! + return 0; + } + } + + /** + * For use only by the current home application, to specify the size of + * wallpaper it would like to use. This allows such applications to have + * a virtual wallpaper that is larger than the physical screen, matching + * the size of their workspace. + * @param minimumWidth Desired minimum width + * @param minimumHeight Desired minimum height + */ + public void suggestDesiredDimensions(int minimumWidth, int minimumHeight) { + try { + sGlobals.mService.setDimensionHints(minimumWidth, minimumHeight); + } catch (RemoteException e) { + } + } + + /** + * Set the position of the current wallpaper within any larger space, when + * that wallpaper is visible behind the given window. The X and Y offsets + * are floating point numbers ranging from 0 to 1, representing where the + * wallpaper should be positioned within the screen space. These only + * make sense when the wallpaper is larger than the screen. + * + * @param windowToken The window who these offsets should be associated + * with, as returned by {@link android.view.View#getWindowToken() + * View.getWindowToken()}. + * @param xOffset The offset along the X dimension, from 0 to 1. + * @param yOffset The offset along the Y dimension, from 0 to 1. + */ + public void setWallpaperOffsets(IBinder windowToken, float xOffset, float yOffset) { + try { + //Log.v(TAG, "Sending new wallpaper offsets from app..."); + ViewRoot.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( + windowToken, xOffset, yOffset, mWallpaperXStep, mWallpaperYStep); + //Log.v(TAG, "...app returning after sending offsets!"); + } catch (RemoteException e) { + // Ignore. + } + } + + /** + * For applications that use multiple virtual screens showing a wallpaper, + * specify the step size between virtual screens. For example, if the + * launcher has 5 virtual screens, it would specify an xStep of 0.5, + * since the X offset for those screens are 0.0, 0.5 and 1.0 + * @param xStep The X offset delta from one screen to the next one + * @param yStep The Y offset delta from one screen to the next one + */ + public void setWallpaperOffsetSteps(float xStep, float yStep) { + mWallpaperXStep = xStep; + mWallpaperYStep = yStep; + } + + /** + * Send an arbitrary command to the current active wallpaper. + * + * @param windowToken The window who these offsets should be associated + * with, as returned by {@link android.view.View#getWindowToken() + * View.getWindowToken()}. + * @param action Name of the command to perform. This must be a scoped + * name to avoid collisions, such as "com.mycompany.wallpaper.DOIT". + * @param x Arbitrary integer argument based on command. + * @param y Arbitrary integer argument based on command. + * @param z Arbitrary integer argument based on command. + * @param extras Optional additional information for the command, or null. + */ + public void sendWallpaperCommand(IBinder windowToken, String action, + int x, int y, int z, Bundle extras) { + try { + //Log.v(TAG, "Sending new wallpaper offsets from app..."); + ViewRoot.getWindowSession(mContext.getMainLooper()).sendWallpaperCommand( + windowToken, action, x, y, z, extras, false); + //Log.v(TAG, "...app returning after sending offsets!"); + } catch (RemoteException e) { + // Ignore. + } + } + + /** + * Clear the offsets previously associated with this window through + * {@link #setWallpaperOffsets(IBinder, float, float)}. This reverts + * the window to its default state, where it does not cause the wallpaper + * to scroll from whatever its last offsets were. + * + * @param windowToken The window who these offsets should be associated + * with, as returned by {@link android.view.View#getWindowToken() + * View.getWindowToken()}. + */ + public void clearWallpaperOffsets(IBinder windowToken) { + try { + ViewRoot.getWindowSession(mContext.getMainLooper()).setWallpaperPosition( + windowToken, -1, -1, -1, -1); + } catch (RemoteException e) { + // Ignore. + } + } + + /** + * Remove any currently set wallpaper, reverting to the system's default + * wallpaper. On success, the intent {@link Intent#ACTION_WALLPAPER_CHANGED} + * is broadcast. + * + * @throws IOException If an error occurs reverting to the default + * wallpaper. + */ + public void clear() throws IOException { + setResource(com.android.internal.R.drawable.default_wallpaper); + } + + static Bitmap generateBitmap(Context context, Bitmap bm, int width, int height) { + if (bm == null) { + return bm; + } + bm.setDensity(DisplayMetrics.DENSITY_DEVICE); + + // This is the final bitmap we want to return. + // XXX We should get the pixel depth from the system (to match the + // physical display depth), when there is a way. + Bitmap newbm = Bitmap.createBitmap(width, height, + Bitmap.Config.RGB_565); + newbm.setDensity(DisplayMetrics.DENSITY_DEVICE); + Canvas c = new Canvas(newbm); + c.setDensity(DisplayMetrics.DENSITY_DEVICE); + Rect targetRect = new Rect(); + targetRect.left = targetRect.top = 0; + targetRect.right = bm.getWidth(); + targetRect.bottom = bm.getHeight(); + + int deltaw = width - targetRect.right; + int deltah = height - targetRect.bottom; + + if (deltaw > 0 || deltah > 0) { + // We need to scale up so it covers the entire + // area. + float scale = 1.0f; + if (deltaw > deltah) { + scale = width / (float)targetRect.right; + } else { + scale = height / (float)targetRect.bottom; + } + targetRect.right = (int)(targetRect.right*scale); + targetRect.bottom = (int)(targetRect.bottom*scale); + deltaw = width - targetRect.right; + deltah = height - targetRect.bottom; + } + + targetRect.offset(deltaw/2, deltah/2); + Paint paint = new Paint(); + paint.setFilterBitmap(true); + paint.setDither(true); + c.drawBitmap(bm, null, targetRect, paint); + + bm.recycle(); + return newbm; + } +} |