diff options
Diffstat (limited to 'services/java/com/android/server/am')
-rw-r--r-- | services/java/com/android/server/am/ActivityManagerService.java | 1082 | ||||
-rw-r--r-- | services/java/com/android/server/am/BatteryStatsService.java | 13 | ||||
-rw-r--r-- | services/java/com/android/server/am/BroadcastRecord.java | 11 | ||||
-rw-r--r-- | services/java/com/android/server/am/ConnectionRecord.java | 8 | ||||
-rw-r--r-- | services/java/com/android/server/am/PendingIntentRecord.java | 18 | ||||
-rw-r--r-- | services/java/com/android/server/am/ProcessRecord.java | 4 | ||||
-rw-r--r-- | services/java/com/android/server/am/ServiceRecord.java | 134 | ||||
-rw-r--r--[-rwxr-xr-x] | services/java/com/android/server/am/UsageStatsService.java | 14 |
8 files changed, 1051 insertions, 233 deletions
diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 5d34d00..c3aeca4 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -39,8 +39,10 @@ import android.app.IInstrumentationWatcher; import android.app.IServiceConnection; import android.app.IThumbnailReceiver; import android.app.Instrumentation; +import android.app.Notification; import android.app.PendingIntent; import android.app.ResultInfo; +import android.app.Service; import android.backup.IBackupManager; import android.content.ActivityNotFoundException; import android.content.ComponentName; @@ -50,6 +52,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.IIntentReceiver; import android.content.IIntentSender; +import android.content.IntentSender; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ConfigurationInfo; @@ -66,6 +69,7 @@ import android.graphics.Bitmap; import android.net.Uri; import android.os.Binder; import android.os.Bundle; +import android.os.Debug; import android.os.Environment; import android.os.FileUtils; import android.os.Handler; @@ -133,9 +137,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen static final boolean DEBUG_SERVICE = localLOGV || false; static final boolean DEBUG_VISBILITY = localLOGV || false; static final boolean DEBUG_PROCESSES = localLOGV || false; + static final boolean DEBUG_PROVIDER = localLOGV || false; static final boolean DEBUG_USER_LEAVING = localLOGV || false; static final boolean DEBUG_RESULTS = localLOGV || false; - static final boolean DEBUG_BACKUP = localLOGV || true; + static final boolean DEBUG_BACKUP = localLOGV || false; + static final boolean DEBUG_CONFIGURATION = localLOGV || false; static final boolean VALIDATE_TOKENS = false; static final boolean SHOW_ACTIVITY_START_TIME = true; @@ -334,6 +340,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Memory pages are 4K. static final int PAGE_SIZE = 4*1024; + // System property defining error report receiver for system apps + static final String SYSTEM_APPS_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.system.apps"; + + // System property defining default error report receiver + static final String DEFAULT_ERROR_RECEIVER_PROPERTY = "ro.error.receiver.default"; + // Corresponding memory levels for above adjustments. final int EMPTY_APP_MEM; final int HIDDEN_APP_MEM; @@ -447,6 +459,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen = new ArrayList<HistoryRecord>(); /** + * Animations that for the current transition have requested not to + * be considered for the transition animation. + */ + final ArrayList<HistoryRecord> mNoAnimActivities + = new ArrayList<HistoryRecord>(); + + /** * List of intents that were used to start the most recent tasks. */ final ArrayList<TaskRecord> mRecentTasks @@ -763,6 +782,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen String mTopData; boolean mSystemReady = false; boolean mBooting = false; + boolean mWaitingUpdate = false; + boolean mDidUpdate = false; Context mContext; @@ -980,6 +1001,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen res.set(0); } } + + ensureBootCompleted(); } break; case SHOW_NOT_RESPONDING_MSG: { synchronized (ActivityManagerService.this) { @@ -1000,13 +1023,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen proc.anrDialog = d; } - ensureScreenEnabled(); + ensureBootCompleted(); } break; case SHOW_FACTORY_ERROR_MSG: { Dialog d = new FactoryErrorDialog( mContext, msg.getData().getCharSequence("msg")); d.show(); - enableScreenAfterBoot(); + ensureBootCompleted(); } break; case UPDATE_CONFIGURATION_MSG: { final ContentResolver resolver = mContext.getContentResolver(); @@ -1070,7 +1093,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // so we need to be conservative and assume it isn't. IBinder token = (IBinder)msg.obj; Log.w(TAG, "Activity idle timeout for " + token); - activityIdleInternal(token, true); + activityIdleInternal(token, true, null); } break; case DESTROY_TIMEOUT_MSG: { IBinder token = (IBinder)msg.obj; @@ -1081,7 +1104,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } break; case IDLE_NOW_MSG: { IBinder token = (IBinder)msg.obj; - activityIdle(token); + activityIdle(token, null); } break; case SERVICE_TIMEOUT_MSG: { if (mDidDexOpt) { @@ -1199,6 +1222,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ApplicationInfo info = mSelf.mContext.getPackageManager().getApplicationInfo( "android", STOCK_PM_FLAGS); + mSystemThread.installSystemApplicationInfo(info); + synchronized (mSelf) { ProcessRecord app = mSelf.newProcessRecordLocked( mSystemThread.getApplicationThread(), info, @@ -1411,6 +1436,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mSimpleProcessManagement = true; } + Log.i(TAG, "Memory class: " + ActivityManager.staticGetMemoryClass()); + MY_PID = Process.myPid(); File dataDir = Environment.getDataDirectory(); @@ -1557,6 +1584,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + long[] cpuSpeedTimes = mProcessStats.getLastCpuSpeedTimes(); final BatteryStatsImpl bstats = mBatteryStatsService.getActiveStatistics(); synchronized(bstats) { synchronized(mPidsSelfLocked) { @@ -1570,11 +1598,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (pr != null) { BatteryStatsImpl.Uid.Proc ps = pr.batteryStats; ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); + ps.addSpeedStepTimes(cpuSpeedTimes); } else { BatteryStatsImpl.Uid.Proc ps = bstats.getProcessStatsLocked(st.name, st.pid); if (ps != null) { ps.addCpuTimeLocked(st.rel_utime, st.rel_stime); + ps.addSpeedStepTimes(cpuSpeedTimes); } } } @@ -1850,12 +1880,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } startProcessLocked(r.processName, r.info.applicationInfo, true, 0, - "activity", r.intent.getComponent()); + "activity", r.intent.getComponent(), false); } private final ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, - String hostingType, ComponentName hostingName) { + String hostingType, ComponentName hostingName, boolean allowWhileBooting) { ProcessRecord app = getProcessRecordLocked(processName, info.uid); // We don't have to do anything more if: // (1) There is an existing application record; and @@ -1908,7 +1938,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // If the system is not ready yet, then hold off on starting this // process until it is. if (!mSystemReady - && (info.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) { + && !isAllowedWhileBooting(info) + && !allowWhileBooting) { if (!mProcessesOnHold.contains(app)) { mProcessesOnHold.add(app); } @@ -1919,6 +1950,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return (app.pid != 0) ? app : null; } + boolean isAllowedWhileBooting(ApplicationInfo ai) { + return (ai.flags&ApplicationInfo.FLAG_PERSISTENT) != 0; + } + private final void startProcessLocked(ProcessRecord app, String hostingType, String hostingNameStr) { if (app.pid > 0 && app.pid != MY_PID) { @@ -2219,13 +2254,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mHandler.sendMessage(msg); } - reportResumedActivity(next); + reportResumedActivityLocked(next); next.thumbnail = null; setFocusedActivityLocked(next); next.resumeKeyDispatchingLocked(); ensureActivitiesVisibleLocked(null, 0); mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); // Mark the point when the activity is resuming // TODO: To be more accurate, the mark should be before the onCreate, @@ -2491,7 +2527,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private void reportResumedActivity(HistoryRecord r) { + private void reportResumedActivityLocked(HistoryRecord r) { //Log.i(TAG, "**** REPORT RESUME: " + r); final int identHash = System.identityHashCode(r); @@ -2542,6 +2578,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Make sure we have executed any pending transitions, since there // should be nothing left to do at this point. mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); return false; } @@ -2552,6 +2589,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Make sure we have executed any pending transitions, since there // should be nothing left to do at this point. mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); return false; } @@ -2614,17 +2652,25 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (prev.finishing) { if (DEBUG_TRANSITION) Log.v(TAG, "Prepare close transition: prev=" + prev); - mWindowManager.prepareAppTransition(prev.task == next.task - ? WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE - : WindowManagerPolicy.TRANSIT_TASK_CLOSE); + if (mNoAnimActivities.contains(prev)) { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + } else { + mWindowManager.prepareAppTransition(prev.task == next.task + ? WindowManagerPolicy.TRANSIT_ACTIVITY_CLOSE + : WindowManagerPolicy.TRANSIT_TASK_CLOSE); + } mWindowManager.setAppWillBeHidden(prev); mWindowManager.setAppVisibility(prev, false); } else { if (DEBUG_TRANSITION) Log.v(TAG, "Prepare open transition: prev=" + prev); - mWindowManager.prepareAppTransition(prev.task == next.task - ? WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN - : WindowManagerPolicy.TRANSIT_TASK_OPEN); + if (mNoAnimActivities.contains(next)) { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + } else { + mWindowManager.prepareAppTransition(prev.task == next.task + ? WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN + : WindowManagerPolicy.TRANSIT_TASK_OPEN); + } } if (false) { mWindowManager.setAppWillBeHidden(prev); @@ -2633,7 +2679,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } else if (mHistory.size() > 1) { if (DEBUG_TRANSITION) Log.v(TAG, "Prepare open transition: no previous"); - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + if (mNoAnimActivities.contains(next)) { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + } else { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + } } if (next.app != null && next.app.thread != null) { @@ -2655,13 +2705,31 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Have the window manager re-evaluate the orientation of // the screen based on the new activity order. - Configuration config = mWindowManager.updateOrientationFromAppTokens( - mConfiguration, - next.mayFreezeScreenLocked(next.app) ? next : null); - if (config != null) { - next.frozenBeforeDestroy = true; - } - if (!updateConfigurationLocked(config, next)) { + boolean updated; + synchronized (this) { + Configuration config = mWindowManager.updateOrientationFromAppTokens( + mConfiguration, + next.mayFreezeScreenLocked(next.app) ? next : null); + if (config != null) { + /* + * Explicitly restore the locale to the one from the + * old configuration, since the one that comes back from + * the window manager has the default (boot) locale. + * + * It looks like previously the locale picker only worked + * by coincidence: usually it would do its setting of + * the locale after the activity transition, so it didn't + * matter that this lost it. With the synchronized + * block now keeping them from happening at the same time, + * this one always would happen second and undo what the + * locale picker had just done. + */ + config.locale = mConfiguration.locale; + next.frozenBeforeDestroy = true; + } + updated = updateConfigurationLocked(config, next); + } + if (!updated) { // The configuration update wasn't able to keep the existing // instance of the activity, and instead started a new one. // We should be all done, but let's just make sure our activity @@ -2675,7 +2743,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Do over! mHandler.sendEmptyMessage(RESUME_TOP_ACTIVITY_MSG); } + setFocusedActivityLocked(next); + ensureActivitiesVisibleLocked(null, 0); mWindowManager.executeAppTransition(); + mNoAnimActivities.clear(); return true; } @@ -2836,9 +2907,18 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } if (DEBUG_TRANSITION) Log.v(TAG, "Prepare open transition: starting " + r); - mWindowManager.prepareAppTransition(newTask - ? WindowManagerPolicy.TRANSIT_TASK_OPEN - : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + mNoAnimActivities.add(r); + } else if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_OPEN); + mNoAnimActivities.remove(r); + } else { + mWindowManager.prepareAppTransition(newTask + ? WindowManagerPolicy.TRANSIT_TASK_OPEN + : WindowManagerPolicy.TRANSIT_ACTIVITY_OPEN); + mNoAnimActivities.remove(r); + } mWindowManager.addAppToken( addPos, r, r.task.taskId, r.info.screenOrientation, r.fullscreen); boolean doShow = true; @@ -2898,7 +2978,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * or null if none was found. */ private final HistoryRecord performClearTaskLocked(int taskId, - HistoryRecord newR, boolean doClear) { + HistoryRecord newR, int launchFlags, boolean doClear) { int i = mHistory.size(); // First find the requested task. @@ -2941,7 +3021,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Finally, if this is a normal launch mode (that is, not // expecting onNewIntent()), then we will finish the current // instance of the activity so a new fresh one can be started. - if (ret.launchMode == ActivityInfo.LAUNCH_MULTIPLE) { + if (ret.launchMode == ActivityInfo.LAUNCH_MULTIPLE + && (launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0) { if (!ret.finishing) { int index = indexOfTokenLocked(ret); if (index >= 0) { @@ -3312,7 +3393,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (callerAtFront) { // We really do want to push this one into the // user's face, right now. - moveTaskToFrontLocked(taskTop.task); + moveTaskToFrontLocked(taskTop.task, r); } } // If the caller has requested that the target task be @@ -3338,7 +3419,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // cases this means we are resetting the task to its // initial state. HistoryRecord top = performClearTaskLocked( - taskTop.task.taskId, r, true); + taskTop.task.taskId, r, launchFlags, true); if (top != null) { if (top.frontOfTask) { // Activity aliases may mean we use different @@ -3481,7 +3562,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // task, but the caller has asked to clear that task if the // activity is already running. HistoryRecord top = performClearTaskLocked( - sourceRecord.task.taskId, r, true); + sourceRecord.task.taskId, r, launchFlags, true); if (top != null) { logStartActivity(LOG_AM_NEW_INTENT, r, top.task); deliverNewIntentLocked(top, r.intent); @@ -3592,6 +3673,36 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + public int startActivityIntentSender(IApplicationThread caller, + IntentSender intent, Intent fillInIntent, String resolvedType, + IBinder resultTo, String resultWho, int requestCode, + int flagsMask, int flagsValues) { + // Refuse possible leaked file descriptors + if (fillInIntent != null && fillInIntent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + IIntentSender sender = intent.getTarget(); + if (!(sender instanceof PendingIntentRecord)) { + throw new IllegalArgumentException("Bad PendingIntent object"); + } + + PendingIntentRecord pir = (PendingIntentRecord)sender; + + synchronized (this) { + // If this is coming from the currently resumed activity, it is + // effectively saying that app switches are allowed at this point. + if (mResumedActivity != null + && mResumedActivity.info.applicationInfo.uid == + Binder.getCallingUid()) { + mAppSwitchesAllowedTime = 0; + } + } + + return pir.sendInner(0, fillInIntent, resolvedType, + null, resultTo, resultWho, requestCode, flagsMask, flagsValues); + } + public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent) { // Refuse possible leaked file descriptors @@ -4108,6 +4219,27 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + public void overridePendingTransition(IBinder token, String packageName, + int enterAnim, int exitAnim) { + synchronized(this) { + int index = indexOfTokenLocked(token); + if (index < 0) { + return; + } + HistoryRecord self = (HistoryRecord)mHistory.get(index); + + final long origId = Binder.clearCallingIdentity(); + + if (self.state == ActivityState.RESUMED + || self.state == ActivityState.PAUSING) { + mWindowManager.overridePendingAppTransition(packageName, + enterAnim, exitAnim); + } + + Binder.restoreCallingIdentity(origId); + } + } + /** * Perform clean-up of service connections in an activity record. */ @@ -4444,7 +4576,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen long now = SystemClock.uptimeMillis(); for (i=0; i<count; i++) { ProcessRecord rec = mLRUProcesses.get(i); - if (rec.thread != null && + if (rec != app && rec.thread != null && (rec.lastLowMemory+GC_MIN_INTERVAL) <= now) { // The low memory report is overriding any current // state for a GC request. Make sure to do @@ -4871,12 +5003,59 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } mWatchers.finishBroadcast(); + mWindowManager.closeSystemDialogs(reason); + + for (i=mHistory.size()-1; i>=0; i--) { + HistoryRecord r = (HistoryRecord)mHistory.get(i); + if ((r.info.flags&ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0) { + finishActivityLocked(r, i, + Activity.RESULT_CANCELED, null, "close-sys"); + } + } + broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, false, false, -1, uid); } Binder.restoreCallingIdentity(origId); } + public Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids) + throws RemoteException { + Debug.MemoryInfo[] infos = new Debug.MemoryInfo[pids.length]; + for (int i=pids.length-1; i>=0; i--) { + infos[i] = new Debug.MemoryInfo(); + Debug.getMemoryInfo(pids[i], infos[i]); + } + return infos; + } + + public void killApplicationProcess(String processName, int uid) { + if (processName == null) { + return; + } + + int callerUid = Binder.getCallingUid(); + // Only the system server can kill an application + if (callerUid == Process.SYSTEM_UID) { + synchronized (this) { + ProcessRecord app = getProcessRecordLocked(processName, uid); + if (app != null) { + try { + app.thread.scheduleSuicide(); + } catch (RemoteException e) { + // If the other end already died, then our work here is done. + } + } else { + Log.w(TAG, "Process/uid not found attempting kill of " + + processName + " / " + uid); + } + } + } else { + throw new SecurityException(callerUid + " cannot kill app process: " + + processName); + } + } + private void restartPackageLocked(final String packageName, int uid) { uninstallPackageLocked(packageName, uid, false); Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED, @@ -5101,8 +5280,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); - List providers = generateApplicationProvidersLocked(app); + boolean normalMode = mSystemReady || isAllowedWhileBooting(app.info); + List providers = normalMode ? generateApplicationProvidersLocked(app) : null; + if (!normalMode) { + Log.i(TAG, "Launching preboot mode app: " + app); + } + if (localLOGV) Log.v( TAG, "New app record " + app + " thread=" + thread.asBinder() + " pid=" + pid); @@ -5118,23 +5302,28 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mWaitForDebugger = mOrigWaitForDebugger; } } + // If the app is being launched for restore or full backup, set it up specially boolean isRestrictedBackupMode = false; if (mBackupTarget != null && mBackupAppName.equals(processName)) { isRestrictedBackupMode = (mBackupTarget.backupMode == BackupRecord.RESTORE) || (mBackupTarget.backupMode == BackupRecord.BACKUP_FULL); } + ensurePackageDexOpt(app.instrumentationInfo != null ? app.instrumentationInfo.packageName : app.info.packageName); if (app.instrumentationClass != null) { ensurePackageDexOpt(app.instrumentationClass.getPackageName()); } + if (DEBUG_CONFIGURATION) Log.v(TAG, "Binding proc " + + processName + " with config " + mConfiguration); thread.bindApplication(processName, app.instrumentationInfo != null ? app.instrumentationInfo : app.info, providers, app.instrumentationClass, app.instrumentationProfileFile, app.instrumentationArguments, app.instrumentationWatcher, testMode, - isRestrictedBackupMode, mConfiguration, getCommonServicesLocked()); + isRestrictedBackupMode || !normalMode, + mConfiguration, getCommonServicesLocked()); updateLRUListLocked(app, false); app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis(); } catch (Exception e) { @@ -5250,9 +5439,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - public final void activityIdle(IBinder token) { + public final void activityIdle(IBinder token, Configuration config) { final long origId = Binder.clearCallingIdentity(); - activityIdleInternal(token, false); + activityIdleInternal(token, false, config); Binder.restoreCallingIdentity(origId); } @@ -5300,10 +5489,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } void enableScreenAfterBoot() { + EventLog.writeEvent(LOG_BOOT_PROGRESS_ENABLE_SCREEN, + SystemClock.uptimeMillis()); mWindowManager.enableScreenAfterBoot(); } - final void activityIdleInternal(IBinder token, boolean fromTimeout) { + final void activityIdleInternal(IBinder token, boolean fromTimeout, + Configuration config) { if (localLOGV) Log.v(TAG, "Activity idle: " + token); ArrayList<HistoryRecord> stops = null; @@ -5326,6 +5518,15 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (index >= 0) { HistoryRecord r = (HistoryRecord)mHistory.get(index); + // This is a hack to semi-deal with a race condition + // in the client where it can be constructed with a + // newer configuration from when we asked it to launch. + // We'll update with whatever configuration it now says + // it used to launch. + if (config != null) { + r.configuration = config; + } + // No longer need to keep the device awake. if (mResumedActivity == r && mLaunchingActivity.isHeld()) { mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); @@ -5410,26 +5611,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } if (booting) { - // Ensure that any processes we had put on hold are now started - // up. - final int NP = mProcessesOnHold.size(); - if (NP > 0) { - ArrayList<ProcessRecord> procs = - new ArrayList<ProcessRecord>(mProcessesOnHold); - for (int ip=0; ip<NP; ip++) { - this.startProcessLocked(procs.get(ip), "on-hold", null); - } - } - if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { - // Tell anyone interested that we are done booting! - synchronized (this) { - broadcastIntentLocked(null, null, - new Intent(Intent.ACTION_BOOT_COMPLETED, null), - null, null, 0, null, null, - android.Manifest.permission.RECEIVE_BOOT_COMPLETED, - false, false, MY_PID, Process.SYSTEM_UID); - } - } + finishBooting(); } trimApplications(); @@ -5437,22 +5619,48 @@ public final class ActivityManagerService extends ActivityManagerNative implemen //mWindowManager.dump(); if (enableScreen) { - EventLog.writeEvent(LOG_BOOT_PROGRESS_ENABLE_SCREEN, - SystemClock.uptimeMillis()); enableScreenAfterBoot(); } } - final void ensureScreenEnabled() { + final void finishBooting() { + // Ensure that any processes we had put on hold are now started + // up. + final int NP = mProcessesOnHold.size(); + if (NP > 0) { + ArrayList<ProcessRecord> procs = + new ArrayList<ProcessRecord>(mProcessesOnHold); + for (int ip=0; ip<NP; ip++) { + this.startProcessLocked(procs.get(ip), "on-hold", null); + } + } + if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { + // Tell anyone interested that we are done booting! + synchronized (this) { + broadcastIntentLocked(null, null, + new Intent(Intent.ACTION_BOOT_COMPLETED, null), + null, null, 0, null, null, + android.Manifest.permission.RECEIVE_BOOT_COMPLETED, + false, false, MY_PID, Process.SYSTEM_UID); + } + } + } + + final void ensureBootCompleted() { + boolean booting; boolean enableScreen; synchronized (this) { + booting = mBooting; + mBooting = false; enableScreen = !mBooted; mBooted = true; } + + if (booting) { + finishBooting(); + } if (enableScreen) { - EventLog.writeEvent(LOG_BOOT_PROGRESS_ENABLE_SCREEN, - SystemClock.uptimeMillis()); enableScreenAfterBoot(); } } @@ -5552,7 +5760,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen public String getCallingPackage(IBinder token) { synchronized (this) { HistoryRecord r = getCallingRecordLocked(token); - return r != null && r.app != null ? r.app.processName : null; + return r != null && r.app != null ? r.info.packageName : null; } } @@ -5604,6 +5812,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen throw new IllegalArgumentException("File descriptors passed in Intent"); } + if (type == INTENT_SENDER_BROADCAST) { + if ((intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { + throw new IllegalArgumentException( + "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); + } + } + synchronized(this) { int callingUid = Binder.getCallingUid(); try { @@ -6807,14 +7022,14 @@ public final class ActivityManagerService extends ActivityManagerNative implemen for (int i=0; i<N; i++) { TaskRecord tr = mRecentTasks.get(i); if (tr.taskId == task) { - moveTaskToFrontLocked(tr); + moveTaskToFrontLocked(tr, null); return; } } for (int i=mHistory.size()-1; i>=0; i--) { HistoryRecord hr = (HistoryRecord)mHistory.get(i); if (hr.task.taskId == task) { - moveTaskToFrontLocked(hr.task); + moveTaskToFrontLocked(hr.task, null); return; } } @@ -6824,7 +7039,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - private final void moveTaskToFrontLocked(TaskRecord tr) { + private final void moveTaskToFrontLocked(TaskRecord tr, HistoryRecord reason) { if (DEBUG_SWITCH) Log.v(TAG, "moveTaskToFront: " + tr); final int task = tr.taskId; @@ -6835,10 +7050,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return; } - if (DEBUG_TRANSITION) Log.v(TAG, - "Prepare to front transition: task=" + tr); - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_FRONT); - ArrayList moved = new ArrayList(); // Applying the affinities may have removed entries from the history, @@ -6867,6 +7078,19 @@ public final class ActivityManagerService extends ActivityManagerNative implemen pos--; } + if (DEBUG_TRANSITION) Log.v(TAG, + "Prepare to front transition: task=" + tr); + if (reason != null && + (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + HistoryRecord r = topRunningActivityLocked(null); + if (r != null) { + mNoAnimActivities.add(r); + } + } else { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_FRONT); + } + mWindowManager.moveAppTokensToTop(moved); if (VALIDATE_TOKENS) { mWindowManager.validateAppTokens(mHistory); @@ -6892,7 +7116,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } final long origId = Binder.clearCallingIdentity(); - moveTaskToBackLocked(task); + moveTaskToBackLocked(task, null); Binder.restoreCallingIdentity(origId); } } @@ -6911,7 +7135,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen final long origId = Binder.clearCallingIdentity(); int taskId = getTaskForActivityLocked(token, !nonRoot); if (taskId >= 0) { - return moveTaskToBackLocked(taskId); + return moveTaskToBackLocked(taskId, null); } Binder.restoreCallingIdentity(origId); } @@ -6929,7 +7153,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen * @param task The taskId to collect and move to the bottom. * @return Returns true if the move completed, false if not. */ - private final boolean moveTaskToBackLocked(int task) { + private final boolean moveTaskToBackLocked(int task, HistoryRecord reason) { Log.i(TAG, "moveTaskToBack: " + task); // If we have a watcher, preflight the move before committing to it. First check @@ -6958,7 +7182,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (DEBUG_TRANSITION) Log.v(TAG, "Prepare to back transition: task=" + task); - mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_BACK); final int N = mHistory.size(); int bottom = 0; @@ -6980,6 +7203,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen pos++; } + if (reason != null && + (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_NONE); + HistoryRecord r = topRunningActivityLocked(null); + if (r != null) { + mNoAnimActivities.add(r); + } + } else { + mWindowManager.prepareAppTransition(WindowManagerPolicy.TRANSIT_TASK_TO_BACK); + } mWindowManager.moveAppTokensToBottom(moved); if (VALIDATE_TOKENS) { mWindowManager.validateAppTokens(mHistory); @@ -7322,10 +7555,19 @@ public final class ActivityManagerService extends ActivityManagerNative implemen final long origId = Binder.clearCallingIdentity(); - // In this case the provider is a single instance, so we can + // In this case the provider instance already exists, so we can // return it right away. if (r != null) { - r.conProviders.add(cpr); + if (DEBUG_PROVIDER) Log.v(TAG, + "Adding provider requested by " + + r.processName + " from process " + + cpr.info.processName); + Integer cnt = r.conProviders.get(cpr); + if (cnt == null) { + r.conProviders.put(cpr, new Integer(1)); + } else { + r.conProviders.put(cpr, new Integer(cnt.intValue()+1)); + } cpr.clients.add(r); } else { cpr.externals++; @@ -7382,10 +7624,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return cpr; } - if (false) { - RuntimeException e = new RuntimeException("foo"); - //Log.w(TAG, "LAUNCHING REMOTE PROVIDER (myuid " + r.info.uid - // + " pruid " + ai.uid + "): " + cpi.className, e); + if (DEBUG_PROVIDER) { + RuntimeException e = new RuntimeException("here"); + Log.w(TAG, "LAUNCHING REMOTE PROVIDER (myuid " + r.info.uid + + " pruid " + cpr.appInfo.uid + "): " + cpr.info.name, e); } // This is single process, and our app is now connecting to it. @@ -7397,14 +7639,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (mLaunchingProviders.get(i) == cpr) { break; } - if (false) { - final ContentProviderRecord rec = - (ContentProviderRecord)mLaunchingProviders.get(i); - if (rec.info.name.equals(cpr.info.name)) { - cpr = rec; - break; - } - } } // If the provider is not already being launched, then get it @@ -7414,7 +7648,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ProcessRecord proc = startProcessLocked(cpi.processName, cpr.appInfo, false, 0, "content provider", new ComponentName(cpi.applicationInfo.packageName, - cpi.name)); + cpi.name), false); if (proc == null) { Log.w(TAG, "Unable to launch app " + cpi.applicationInfo.packageName + "/" @@ -7435,7 +7669,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mProvidersByName.put(name, cpr); if (r != null) { - r.conProviders.add(cpr); + if (DEBUG_PROVIDER) Log.v(TAG, + "Adding provider requested by " + + r.processName + " from process " + + cpr.info.processName); + Integer cnt = r.conProviders.get(cpr); + if (cnt == null) { + r.conProviders.put(cpr, new Integer(1)); + } else { + r.conProviders.put(cpr, new Integer(cnt.intValue()+1)); + } cpr.clients.add(r); } else { cpr.externals++; @@ -7489,8 +7732,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized (this) { ContentProviderRecord cpr = (ContentProviderRecord)mProvidersByName.get(name); if(cpr == null) { - //remove from mProvidersByClass - if(localLOGV) Log.v(TAG, name+" content provider not found in providers list"); + // remove from mProvidersByClass + if (DEBUG_PROVIDER) Log.v(TAG, name + + " provider not found in providers list"); return; } final ProcessRecord r = getRecordForAppLocked(caller); @@ -7500,16 +7744,24 @@ public final class ActivityManagerService extends ActivityManagerNative implemen " when removing content provider " + name); } //update content provider record entry info - ContentProviderRecord localCpr = (ContentProviderRecord) mProvidersByClass.get(cpr.info.name); - if(localLOGV) Log.v(TAG, "Removing content provider requested by "+ - r.info.processName+" from process "+localCpr.appInfo.processName); - if(localCpr.appInfo.processName == r.info.processName) { + ContentProviderRecord localCpr = (ContentProviderRecord) + mProvidersByClass.get(cpr.info.name); + if (DEBUG_PROVIDER) Log.v(TAG, "Removing provider requested by " + + r.info.processName + " from process " + + localCpr.appInfo.processName); + if (localCpr.app == r) { //should not happen. taken care of as a local provider - if(localLOGV) Log.v(TAG, "local provider doing nothing Ignoring other names"); + Log.w(TAG, "removeContentProvider called on local provider: " + + cpr.info.name + " in process " + r.processName); return; } else { - localCpr.clients.remove(r); - r.conProviders.remove(localCpr); + Integer cnt = r.conProviders.get(localCpr); + if (cnt == null || cnt.intValue() <= 1) { + localCpr.clients.remove(r); + r.conProviders.remove(localCpr); + } else { + r.conProviders.put(localCpr, new Integer(cnt.intValue()-1)); + } } updateOomAdjLocked(); } @@ -8099,7 +8351,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - systemReady(); + systemReady(null); } private void retrieveSettings() { @@ -8121,6 +8373,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // This happens before any activities are started, so we can // change mConfiguration in-place. mConfiguration.updateFrom(configuration); + if (DEBUG_CONFIGURATION) Log.v(TAG, "Initial config: " + mConfiguration); } } @@ -8129,7 +8382,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return mSystemReady; } - public void systemReady() { + public void systemReady(final Runnable goingCallback) { // In the simulator, startRunning will never have been called, which // normally sets a few crucial variables. Do it here instead. if (!Process.supportsProcesses()) { @@ -8139,19 +8392,97 @@ public final class ActivityManagerService extends ActivityManagerNative implemen synchronized(this) { if (mSystemReady) { + if (goingCallback != null) goingCallback.run(); return; } + + // Check to see if there are any update receivers to run. + if (!mDidUpdate) { + if (mWaitingUpdate) { + return; + } + Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED); + List<ResolveInfo> ris = null; + try { + ris = ActivityThread.getPackageManager().queryIntentReceivers( + intent, null, 0); + } catch (RemoteException e) { + } + if (ris != null) { + for (int i=ris.size()-1; i>=0; i--) { + if ((ris.get(i).activityInfo.applicationInfo.flags + &ApplicationInfo.FLAG_SYSTEM) == 0) { + ris.remove(i); + } + } + intent.addFlags(Intent.FLAG_RECEIVER_BOOT_UPGRADE); + for (int i=0; i<ris.size(); i++) { + ActivityInfo ai = ris.get(i).activityInfo; + intent.setComponent(new ComponentName(ai.packageName, ai.name)); + IIntentReceiver finisher = null; + if (i == 0) { + finisher = new IIntentReceiver.Stub() { + public void performReceive(Intent intent, int resultCode, + String data, Bundle extras, boolean ordered, + boolean sticky) + throws RemoteException { + synchronized (ActivityManagerService.this) { + mDidUpdate = true; + } + systemReady(goingCallback); + } + }; + } + Log.i(TAG, "Sending system update to: " + intent.getComponent()); + broadcastIntentLocked(null, null, intent, null, finisher, + 0, null, null, null, true, false, MY_PID, Process.SYSTEM_UID); + if (i == 0) { + mWaitingUpdate = true; + } + } + } + if (mWaitingUpdate) { + return; + } + mDidUpdate = true; + } + mSystemReady = true; if (!mStartRunning) { return; } } - if (Config.LOGD) Log.d(TAG, "Start running!"); + ArrayList<ProcessRecord> procsToKill = null; + synchronized(mPidsSelfLocked) { + for (int i=mPidsSelfLocked.size()-1; i>=0; i--) { + ProcessRecord proc = mPidsSelfLocked.valueAt(i); + if (!isAllowedWhileBooting(proc.info)){ + if (procsToKill == null) { + procsToKill = new ArrayList<ProcessRecord>(); + } + procsToKill.add(proc); + } + } + } + + if (procsToKill != null) { + synchronized(this) { + for (int i=procsToKill.size()-1; i>=0; i--) { + ProcessRecord proc = procsToKill.get(i); + Log.i(TAG, "Removing system update proc: " + proc); + removeProcessLocked(proc, true); + } + } + } + + Log.i(TAG, "System now ready"); EventLog.writeEvent(LOG_BOOT_PROGRESS_AMS_READY, SystemClock.uptimeMillis()); synchronized(this) { + // Make sure we have no pre-ready processes sitting around. + if (mFactoryTest == SystemServer.FACTORY_TEST_LOW_LEVEL) { ResolveInfo ri = mContext.getPackageManager() .resolveActivity(new Intent(Intent.ACTION_FACTORY_TEST), @@ -8187,6 +8518,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen retrieveSettings(); + if (goingCallback != null) goingCallback.run(); + synchronized (this) { if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { try { @@ -8209,6 +8542,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + // Start up initial activity. + mBooting = true; + try { if (ActivityThread.getPackageManager().hasSystemUidErrors()) { Message msg = Message.obtain(); @@ -8218,8 +8554,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } catch (RemoteException e) { } - // Start up initial activity. - mBooting = true; resumeTopActivityLocked(null); } } @@ -8235,28 +8569,70 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } private ComponentName getErrorReportReceiver(ProcessRecord app) { + // check if error reporting is enabled in Gservices + int enabled = Settings.Gservices.getInt(mContext.getContentResolver(), + Settings.Gservices.SEND_ACTION_APP_ERROR, 0); + if (enabled == 0) { + return null; + } + IPackageManager pm = ActivityThread.getPackageManager(); + try { - // was an installer package name specified when this app was - // installed? - String installerPackageName = pm.getInstallerPackageName(app.info.packageName); - if (installerPackageName == null) { - return null; + // look for receiver in the installer package + String candidate = pm.getInstallerPackageName(app.info.packageName); + ComponentName result = getErrorReportReceiver(pm, app.info.packageName, candidate); + if (result != null) { + return result; } - // is there an Activity in this package that handles ACTION_APP_ERROR? - Intent intent = new Intent(Intent.ACTION_APP_ERROR); - intent.setPackage(installerPackageName); - ResolveInfo info = pm.resolveIntent(intent, null, 0); - if (info == null || info.activityInfo == null) { - return null; + // if the error app is on the system image, look for system apps + // error receiver + if ((app.info.flags&ApplicationInfo.FLAG_SYSTEM) != 0) { + candidate = SystemProperties.get(SYSTEM_APPS_ERROR_RECEIVER_PROPERTY); + result = getErrorReportReceiver(pm, app.info.packageName, candidate); + if (result != null) { + return result; + } } - return new ComponentName(installerPackageName, info.activityInfo.name); + // if there is a default receiver, try that + candidate = SystemProperties.get(DEFAULT_ERROR_RECEIVER_PROPERTY); + return getErrorReportReceiver(pm, app.info.packageName, candidate); } catch (RemoteException e) { - // will return null and no error report will be delivered + // should not happen + Log.e(TAG, "error talking to PackageManager", e); + return null; } - return null; + } + + /** + * Return activity in receiverPackage that handles ACTION_APP_ERROR. + * + * @param pm PackageManager isntance + * @param errorPackage package which caused the error + * @param receiverPackage candidate package to receive the error + * @return activity component within receiverPackage which handles + * ACTION_APP_ERROR, or null if not found + */ + private ComponentName getErrorReportReceiver(IPackageManager pm, String errorPackage, + String receiverPackage) throws RemoteException { + if (receiverPackage == null || receiverPackage.length() == 0) { + return null; + } + + // break the loop if it's the error report receiver package that crashed + if (receiverPackage.equals(errorPackage)) { + return null; + } + + Intent intent = new Intent(Intent.ACTION_APP_ERROR); + intent.setPackage(receiverPackage); + ResolveInfo info = pm.resolveIntent(intent, null, 0); + if (info == null || info.activityInfo == null) { + return null; + } + return new ComponentName(receiverPackage, info.activityInfo.name); } void makeAppNotRespondingLocked(ProcessRecord app, @@ -8578,6 +8954,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen report.crashInfo.throwFileName = trace.getFileName(); report.crashInfo.throwClassName = trace.getClassName(); report.crashInfo.throwMethodName = trace.getMethodName(); + report.crashInfo.throwLineNumber = trace.getLineNumber(); } else if (r.notResponding) { report.type = ApplicationErrorReport.TYPE_ANR; report.anrInfo = new ApplicationErrorReport.AnrInfo(); @@ -8645,6 +9022,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ActivityManager.RunningAppProcessInfo currApp = new ActivityManager.RunningAppProcessInfo(app.processName, app.pid, app.getPackageList()); + currApp.uid = app.info.uid; int adj = app.curAdj; if (adj >= CONTENT_PROVIDER_ADJ) { currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_EMPTY; @@ -8661,6 +9039,16 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } else { currApp.importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND; } + currApp.importanceReasonCode = app.adjTypeCode; + if (app.adjSource instanceof ProcessRecord) { + currApp.importanceReasonPid = ((ProcessRecord)app.adjSource).pid; + } else if (app.adjSource instanceof HistoryRecord) { + HistoryRecord r = (HistoryRecord)app.adjSource; + if (r.app != null) currApp.importanceReasonPid = r.app.pid; + } + if (app.adjTarget instanceof ComponentName) { + currApp.importanceReasonComponent = (ComponentName)app.adjTarget; + } //Log.v(TAG, "Proc " + app.processName + ": imp=" + currApp.importance // + " lru=" + currApp.lru); if (runList == null) { @@ -8830,7 +9218,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen pw.print(" lowMem="); pw.print(proc.reportLowMemory); pw.print(", last gced="); pw.print(now-proc.lastRequestedGc); - pw.print(" ms ago, last lowMwm="); + pw.print(" ms ago, last lowMem="); pw.print(now-proc.lastLowMemory); pw.println(" ms ago"); @@ -9384,7 +9772,9 @@ public final class ActivityManagerService extends ActivityManagerNative implemen sr.app = null; sr.executeNesting = 0; mStoppingServices.remove(sr); - if (sr.bindings.size() > 0) { + + boolean hasClients = sr.bindings.size() > 0; + if (hasClients) { Iterator<IntentBindRecord> bindings = sr.bindings.values().iterator(); while (bindings.hasNext()) { @@ -9405,7 +9795,20 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } else if (!allowRestart) { bringDownServiceLocked(sr, true); } else { - scheduleServiceRestartLocked(sr); + boolean canceled = scheduleServiceRestartLocked(sr, true); + + // Should the service remain running? Note that in the + // extreme case of so many attempts to deliver a command + // that it failed, that we also will stop it here. + if (sr.startRequested && (sr.stopIfKilled || canceled)) { + if (sr.pendingStarts.size() == 0) { + sr.startRequested = false; + if (!hasClients) { + // Whoops, no reason to restart! + bringDownServiceLocked(sr, true); + } + } + } } } @@ -9468,6 +9871,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mLRUProcesses.remove(index); } + mProcessesToGc.remove(app); + // Dismiss any open dialogs. if (app.crashDialog != null) { app.crashDialog.dismiss(); @@ -9545,7 +9950,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Unregister from connected content providers. if (!app.conProviders.isEmpty()) { - Iterator it = app.conProviders.iterator(); + Iterator it = app.conProviders.keySet().iterator(); while (it.hasNext()) { ContentProviderRecord cpr = (ContentProviderRecord)it.next(); cpr.clients.remove(app); @@ -9648,6 +10053,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (r.app != null) { info.pid = r.app.pid; } + info.uid = r.appInfo.uid; info.process = r.processName; info.foreground = r.isForeground; info.activeSince = r.createTime; @@ -9655,6 +10061,25 @@ public final class ActivityManagerService extends ActivityManagerNative implemen info.clientCount = r.connections.size(); info.crashCount = r.crashCount; info.lastActivityTime = r.lastActivity; + if (r.isForeground) { + info.flags |= ActivityManager.RunningServiceInfo.FLAG_FOREGROUND; + } + if (r.startRequested) { + info.flags |= ActivityManager.RunningServiceInfo.FLAG_STARTED; + } + if (r.app != null && r.app.pid == Process.myPid()) { + info.flags |= ActivityManager.RunningServiceInfo.FLAG_SYSTEM_PROCESS; + } + if (r.app != null && r.app.persistent) { + info.flags |= ActivityManager.RunningServiceInfo.FLAG_PERSISTENT_PROCESS; + } + for (ConnectionRecord conn : r.connections.values()) { + if (conn.clientLabel != 0) { + info.clientPackage = conn.binding.client.info.packageName; + info.clientLabel = conn.clientLabel; + break; + } + } return info; } @@ -9683,6 +10108,20 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + public PendingIntent getRunningServiceControlPanel(ComponentName name) { + synchronized (this) { + ServiceRecord r = mServices.get(name); + if (r != null) { + for (ConnectionRecord conn : r.connections.values()) { + if (conn.clientIntent != null) { + return conn.clientIntent; + } + } + } + } + return null; + } + private final ServiceRecord findServiceLocked(ComponentName name, IBinder token) { ServiceRecord r = mServices.get(name); @@ -9844,35 +10283,55 @@ public final class ActivityManagerService extends ActivityManagerNative implemen private final void sendServiceArgsLocked(ServiceRecord r, boolean oomAdjusted) { - final int N = r.startArgs.size(); + final int N = r.pendingStarts.size(); if (N == 0) { return; } - final int BASEID = r.lastStartId - N + 1; int i = 0; while (i < N) { try { - Intent args = r.startArgs.get(i); + ServiceRecord.StartItem si = r.pendingStarts.get(i); if (DEBUG_SERVICE) Log.v(TAG, "Sending arguments to service: " - + r.name + " " + r.intent + " args=" + args); + + r.name + " " + r.intent + " args=" + si.intent); + if (si.intent == null && N > 1) { + // If somehow we got a dummy start at the front, then + // just drop it here. + i++; + continue; + } bumpServiceExecutingLocked(r); if (!oomAdjusted) { oomAdjusted = true; updateOomAdjLocked(r.app); } - r.app.thread.scheduleServiceArgs(r, BASEID+i, args); + int flags = 0; + if (si.deliveryCount > 0) { + flags |= Service.START_FLAG_RETRY; + } + if (si.doneExecutingCount > 0) { + flags |= Service.START_FLAG_REDELIVERY; + } + r.app.thread.scheduleServiceArgs(r, si.id, flags, si.intent); + si.deliveredTime = SystemClock.uptimeMillis(); + r.deliveredStarts.add(si); + si.deliveryCount++; i++; + } catch (RemoteException e) { + // Remote process gone... we'll let the normal cleanup take + // care of this. + break; } catch (Exception e) { + Log.w(TAG, "Unexpected exception", e); break; } } if (i == N) { - r.startArgs.clear(); + r.pendingStarts.clear(); } else { while (i > 0) { - r.startArgs.remove(0); i--; + r.pendingStarts.remove(i); } } } @@ -9928,44 +10387,91 @@ public final class ActivityManagerService extends ActivityManagerNative implemen try { if (DEBUG_SERVICE) Log.v(TAG, "Scheduling start service: " + r.name + " " + r.intent); + mStringBuilder.setLength(0); + r.intent.getIntent().toShortString(mStringBuilder, false, true); EventLog.writeEvent(LOG_AM_CREATE_SERVICE, System.identityHashCode(r), r.shortName, - r.intent.getIntent().toString(), r.app.pid); + mStringBuilder.toString(), r.app.pid); synchronized (r.stats.getBatteryStats()) { r.stats.startLaunchedLocked(); } ensurePackageDexOpt(r.serviceInfo.packageName); app.thread.scheduleCreateService(r, r.serviceInfo); + r.postNotification(); created = true; } finally { if (!created) { app.services.remove(r); - scheduleServiceRestartLocked(r); + scheduleServiceRestartLocked(r, false); } } requestServiceBindingsLocked(r); + + // If the service is in the started state, and there are no + // pending arguments, then fake up one so its onStartCommand() will + // be called. + if (r.startRequested && r.callStart && r.pendingStarts.size() == 0) { + r.lastStartId++; + if (r.lastStartId < 1) { + r.lastStartId = 1; + } + r.pendingStarts.add(new ServiceRecord.StartItem(r.lastStartId, null)); + } + sendServiceArgsLocked(r, true); } - private final void scheduleServiceRestartLocked(ServiceRecord r) { + private final boolean scheduleServiceRestartLocked(ServiceRecord r, + boolean allowCancel) { + boolean canceled = false; + final long now = SystemClock.uptimeMillis(); + long minDuration = SERVICE_RESTART_DURATION; + long resetTime = SERVICE_RESET_RUN_DURATION; + + // Any delivered but not yet finished starts should be put back + // on the pending list. + final int N = r.deliveredStarts.size(); + if (N > 0) { + for (int i=N-1; i>=0; i--) { + ServiceRecord.StartItem si = r.deliveredStarts.get(i); + if (si.intent == null) { + // We'll generate this again if needed. + } else if (!allowCancel || (si.deliveryCount < ServiceRecord.MAX_DELIVERY_COUNT + && si.doneExecutingCount < ServiceRecord.MAX_DONE_EXECUTING_COUNT)) { + r.pendingStarts.add(0, si); + long dur = SystemClock.uptimeMillis() - si.deliveredTime; + dur *= 2; + if (minDuration < dur) minDuration = dur; + if (resetTime < dur) resetTime = dur; + } else { + Log.w(TAG, "Canceling start item " + si.intent + " in service " + + r.name); + canceled = true; + } + } + r.deliveredStarts.clear(); + } r.totalRestartCount++; if (r.restartDelay == 0) { r.restartCount++; - r.restartDelay = SERVICE_RESTART_DURATION; + r.restartDelay = minDuration; } else { // If it has been a "reasonably long time" since the service // was started, then reset our restart duration back to // the beginning, so we don't infinitely increase the duration // on a service that just occasionally gets killed (which is // a normal case, due to process being killed to reclaim memory). - if (now > (r.restartTime+SERVICE_RESET_RUN_DURATION)) { + if (now > (r.restartTime+resetTime)) { r.restartCount = 1; - r.restartDelay = SERVICE_RESTART_DURATION; + r.restartDelay = minDuration; } else { r.restartDelay *= SERVICE_RESTART_DURATION_FACTOR; + if (r.restartDelay < minDuration) { + r.restartDelay = minDuration; + } } } @@ -9994,6 +10500,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen mRestartingServices.add(r); } + r.cancelNotification(); + mHandler.removeCallbacks(r.restarter); mHandler.postAtTime(r.restarter, r.nextRestartTime); r.nextRestartTime = SystemClock.uptimeMillis() + r.restartDelay; @@ -10006,6 +10514,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen msg.what = SERVICE_ERROR_MSG; msg.obj = r; mHandler.sendMessage(msg); + + return canceled; } final void performServiceRestartLocked(ServiceRecord r) { @@ -10030,7 +10540,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen //Log.i(TAG, "Bring up service:"); //r.dump(" "); - if (r.app != null) { + if (r.app != null && r.app.thread != null) { sendServiceArgsLocked(r, false); return true; } @@ -10061,20 +10571,22 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // restart the application. } + // Not running -- get it started, and enqueue this service record + // to be executed when the app comes up. + if (startProcessLocked(appName, r.appInfo, true, intentFlags, + "service", r.name, false) == null) { + Log.w(TAG, "Unable to launch app " + + r.appInfo.packageName + "/" + + r.appInfo.uid + " for service " + + r.intent.getIntent() + ": process is bad"); + bringDownServiceLocked(r, true); + return false; + } + if (!mPendingServices.contains(r)) { - // Not running -- get it started, and enqueue this service record - // to be executed when the app comes up. - if (startProcessLocked(appName, r.appInfo, true, intentFlags, - "service", r.name) == null) { - Log.w(TAG, "Unable to launch app " - + r.appInfo.packageName + "/" - + r.appInfo.uid + " for service " - + r.intent.getIntent() + ": process is bad"); - bringDownServiceLocked(r, true); - return false; - } mPendingServices.add(r); } + return true; } @@ -10162,15 +10674,24 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + r.cancelNotification(); + r.isForeground = false; + r.foregroundId = 0; + r.foregroundNoti = null; + + // Clear start entries. + r.deliveredStarts.clear(); + r.pendingStarts.clear(); + if (r.app != null) { synchronized (r.stats.getBatteryStats()) { r.stats.stopLaunchedLocked(); } r.app.services.remove(r); if (r.app.thread != null) { - updateServiceForegroundLocked(r.app, false); try { - Log.i(TAG, "Stopping service: " + r.shortName); + if (DEBUG_SERVICE) Log.v(TAG, + "Stopping service: " + r.shortName); bumpServiceExecutingLocked(r); mStoppingServices.add(r); updateOomAdjLocked(r.app); @@ -10180,6 +10701,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + r.shortName, e); serviceDoneExecutingLocked(r, true); } + updateServiceForegroundLocked(r.app, false); } else { if (DEBUG_SERVICE) Log.v( TAG, "Removed service that has no process: " + r.shortName); @@ -10223,11 +10745,12 @@ public final class ActivityManagerService extends ActivityManagerNative implemen + r.shortName); } r.startRequested = true; - r.startArgs.add(service); + r.callStart = false; r.lastStartId++; if (r.lastStartId < 1) { r.lastStartId = 1; } + r.pendingStarts.add(new ServiceRecord.StartItem(r.lastStartId, service)); r.lastActivity = SystemClock.uptimeMillis(); synchronized (r.stats.getBatteryStats()) { r.stats.startRunningLocked(); @@ -10295,6 +10818,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen r.record.stats.stopRunningLocked(); } r.record.startRequested = false; + r.record.callStart = false; final long origId = Binder.clearCallingIdentity(); bringDownServiceLocked(r.record, false); Binder.restoreCallingIdentity(origId); @@ -10343,10 +10867,35 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (DEBUG_SERVICE) Log.v(TAG, "stopServiceToken: " + className + " " + token + " startId=" + startId); ServiceRecord r = findServiceLocked(className, token); - if (r != null && (startId < 0 || r.lastStartId == startId)) { + if (r != null) { + if (startId >= 0) { + // Asked to only stop if done with all work. Note that + // to avoid leaks, we will take this as dropping all + // start items up to and including this one. + ServiceRecord.StartItem si = r.findDeliveredStart(startId, false); + if (si != null) { + while (r.deliveredStarts.size() > 0) { + if (r.deliveredStarts.remove(0) == si) { + break; + } + } + } + + if (r.lastStartId != startId) { + return false; + } + + if (r.deliveredStarts.size() > 0) { + Log.w(TAG, "stopServiceToken startId " + startId + + " is last, but have " + r.deliveredStarts.size() + + " remaining args"); + } + } + synchronized (r.stats.getBatteryStats()) { r.stats.stopRunningLocked(); r.startRequested = false; + r.callStart = false; } final long origId = Binder.clearCallingIdentity(); bringDownServiceLocked(r, false); @@ -10358,20 +10907,45 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } public void setServiceForeground(ComponentName className, IBinder token, - boolean isForeground) { + int id, Notification notification, boolean removeNotification) { + final long origId = Binder.clearCallingIdentity(); + try { synchronized(this) { ServiceRecord r = findServiceLocked(className, token); if (r != null) { - if (r.isForeground != isForeground) { - final long origId = Binder.clearCallingIdentity(); - r.isForeground = isForeground; + if (id != 0) { + if (notification == null) { + throw new IllegalArgumentException("null notification"); + } + if (r.foregroundId != id) { + r.cancelNotification(); + r.foregroundId = id; + } + notification.flags |= Notification.FLAG_FOREGROUND_SERVICE; + r.foregroundNoti = notification; + r.isForeground = true; + r.postNotification(); if (r.app != null) { updateServiceForegroundLocked(r.app, true); } - Binder.restoreCallingIdentity(origId); + } else { + if (r.isForeground) { + r.isForeground = false; + if (r.app != null) { + updateServiceForegroundLocked(r.app, true); + } + } + if (removeNotification) { + r.cancelNotification(); + r.foregroundId = 0; + r.foregroundNoti = null; + } } } } + } finally { + Binder.restoreCallingIdentity(origId); + } } public void updateServiceForegroundLocked(ProcessRecord proc, boolean oomAdj) { @@ -10420,6 +10994,29 @@ public final class ActivityManagerService extends ActivityManagerNative implemen activity = (HistoryRecord)mHistory.get(aindex); } + int clientLabel = 0; + PendingIntent clientIntent = null; + + if (callerApp.info.uid == Process.SYSTEM_UID) { + // Hacky kind of thing -- allow system stuff to tell us + // what they are, so we can report this elsewhere for + // others to know why certain services are running. + try { + clientIntent = (PendingIntent)service.getParcelableExtra( + Intent.EXTRA_CLIENT_INTENT); + } catch (RuntimeException e) { + } + if (clientIntent != null) { + clientLabel = service.getIntExtra(Intent.EXTRA_CLIENT_LABEL, 0); + if (clientLabel != 0) { + // There are no useful extras in the intent, trash them. + // System code calling with this stuff just needs to know + // this will happen. + service = service.cloneFilter(); + } + } + } + ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, Binder.getCallingPid(), Binder.getCallingUid()); @@ -10440,7 +11037,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp); ConnectionRecord c = new ConnectionRecord(b, activity, - connection, flags); + connection, flags, clientLabel, clientIntent); IBinder binder = connection.asBinder(); s.connections.put(binder, c); @@ -10665,7 +11262,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } - public void serviceDoneExecuting(IBinder token) { + public void serviceDoneExecuting(IBinder token, int type, int startId, int res) { synchronized(this) { if (!(token instanceof ServiceRecord)) { throw new IllegalArgumentException("Invalid service token"); @@ -10683,6 +11280,51 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return; } + if (type == 1) { + // This is a call from a service start... take care of + // book-keeping. + r.callStart = true; + switch (res) { + case Service.START_STICKY_COMPATIBILITY: + case Service.START_STICKY: { + // We are done with the associated start arguments. + r.findDeliveredStart(startId, true); + // Don't stop if killed. + r.stopIfKilled = false; + break; + } + case Service.START_NOT_STICKY: { + // We are done with the associated start arguments. + r.findDeliveredStart(startId, true); + if (r.lastStartId == startId) { + // There is no more work, and this service + // doesn't want to hang around if killed. + r.stopIfKilled = true; + } + break; + } + case Service.START_REDELIVER_INTENT: { + // We'll keep this item until they explicitly + // call stop for it, but keep track of the fact + // that it was delivered. + ServiceRecord.StartItem si = r.findDeliveredStart(startId, false); + if (si != null) { + si.deliveryCount = 0; + si.doneExecutingCount++; + // Don't stop if killed. + r.stopIfKilled = true; + } + break; + } + default: + throw new IllegalArgumentException( + "Unknown service start result: " + res); + } + if (res == Service.START_STICKY_COMPATIBILITY) { + r.callStart = false; + } + } + final long origId = Binder.clearCallingIdentity(); serviceDoneExecutingLocked(r, inStopping); Binder.restoreCallingIdentity(origId); @@ -10761,7 +11403,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ComponentName hostingName = new ComponentName(app.packageName, app.backupAgentName); // startProcessLocked() returns existing proc's record if it's already running ProcessRecord proc = startProcessLocked(app.processName, app, - false, 0, "backup", hostingName); + false, 0, "backup", hostingName, false); if (proc == null) { Log.e(TAG, "Unable to start backup agent process " + r); return false; @@ -10781,8 +11423,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen try { proc.thread.scheduleCreateBackupAgent(app, backupMode); } catch (RemoteException e) { - // !!! TODO: notify the backup manager that we crashed, or rely on - // death notices, or...? + // Will time out on the backup manager side } } else { if (DEBUG_BACKUP) Log.v(TAG, "Agent proc not running, waiting for attach"); @@ -10969,7 +11610,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Intent intent = (Intent)allSticky.get(i); BroadcastRecord r = new BroadcastRecord(intent, null, null, -1, -1, null, receivers, null, 0, null, null, - false); + false, true); if (mParallelBroadcasts.size() == 0) { scheduleBroadcastsLocked(); } @@ -11194,7 +11835,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen BroadcastRecord r = new BroadcastRecord(intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, registeredReceivers, resultTo, resultCode, resultData, map, - ordered); + ordered, false); if (DEBUG_BROADCAST) Log.v( TAG, "Enqueueing parallel broadcast " + r + ": prev had " + mParallelBroadcasts.size()); @@ -11273,7 +11914,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen || resultTo != null) { BroadcastRecord r = new BroadcastRecord(intent, callerApp, callerPackage, callingPid, callingUid, requiredPermission, - receivers, resultTo, resultCode, resultData, map, ordered); + receivers, resultTo, resultCode, resultData, map, ordered, false); if (DEBUG_BROADCAST) Log.v( TAG, "Enqueueing ordered broadcast " + r + ": prev had " + mOrderedBroadcasts.size()); @@ -11298,10 +11939,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } synchronized(this) { + int flags = intent.getFlags(); + if (!mSystemReady) { // if the caller really truly claims to know what they're doing, go // ahead and allow the broadcast without launching any receivers - int flags = intent.getFlags(); if ((flags&Intent.FLAG_RECEIVER_REGISTERED_ONLY_BEFORE_BOOT) != 0) { intent = new Intent(intent); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); @@ -11312,6 +11954,11 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } + if ((flags&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0) { + throw new IllegalArgumentException( + "Can't use FLAG_RECEIVER_BOOT_UPGRADE here"); + } + final ProcessRecord callerApp = getRecordForAppLocked(caller); final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); @@ -11571,15 +12218,15 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } static void performReceive(ProcessRecord app, IIntentReceiver receiver, - Intent intent, int resultCode, String data, - Bundle extras, boolean ordered) throws RemoteException { + Intent intent, int resultCode, String data, Bundle extras, + boolean ordered, boolean sticky) throws RemoteException { if (app != null && app.thread != null) { // If we have an app thread, do the call through that so it is // correctly ordered with other one-way calls. app.thread.scheduleRegisteredReceiver(receiver, intent, resultCode, - data, extras, ordered); + data, extras, ordered, sticky); } else { - receiver.performReceive(intent, resultCode, data, extras, ordered); + receiver.performReceive(intent, resultCode, data, extras, ordered, sticky); } } @@ -11643,7 +12290,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } performReceive(filter.receiverList.app, filter.receiverList.receiver, new Intent(r.intent), r.resultCode, - r.resultData, r.resultExtras, r.ordered); + r.resultData, r.resultExtras, r.ordered, r.sticky); if (ordered) { r.state = BroadcastRecord.CALL_DONE_RECEIVE; } @@ -11698,8 +12345,10 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // broadcast, then do nothing at this point. Just in case, we // check that the process we're waiting for still exists. if (mPendingBroadcast != null) { - Log.i(TAG, "processNextBroadcast: waiting for " - + mPendingBroadcast.curApp); + if (DEBUG_BROADCAST_LIGHT) { + Log.v(TAG, "processNextBroadcast: waiting for " + + mPendingBroadcast.curApp); + } boolean isDead; synchronized (mPidsSelfLocked) { @@ -11774,7 +12423,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } performReceive(r.callerApp, r.resultTo, new Intent(r.intent), r.resultCode, - r.resultData, r.resultExtras, false); + r.resultData, r.resultExtras, false, false); } catch (RemoteException e) { Log.w(TAG, "Failure sending broadcast result of " + r.intent, e); } @@ -11906,12 +12555,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // restart the application. } - // Not running -- get it started, and enqueue this history record - // to be executed when the app comes up. + // Not running -- get it started, to be executed when the app comes up. if ((r.curApp=startProcessLocked(targetProcess, info.activityInfo.applicationInfo, true, r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, - "broadcast", r.curComponent)) == null) { + "broadcast", r.curComponent, + (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0)) + == null) { // Ah, this recipient is unavailable. Finish it if necessary, // and mark the broadcast record as ready for the next. Log.w(TAG, "Unable to launch app " @@ -12119,7 +12769,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen Configuration newConfig = new Configuration(mConfiguration); changes = newConfig.updateFrom(values); if (changes != 0) { - if (DEBUG_SWITCH) { + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) { Log.i(TAG, "Updating configuration to: " + values); } @@ -12132,6 +12782,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } mConfiguration = newConfig; + Log.i(TAG, "Config changed: " + newConfig); Message msg = mHandler.obtainMessage(UPDATE_CONFIGURATION_MSG); msg.obj = new Configuration(mConfiguration); @@ -12142,6 +12793,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen ProcessRecord app = mLRUProcesses.get(i); try { if (app.thread != null) { + if (DEBUG_CONFIGURATION) Log.v(TAG, "Sending to proc " + + app.processName + " new config " + mConfiguration); app.thread.scheduleConfigurationChanged(mConfiguration); } } catch (Exception e) { @@ -12211,6 +12864,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen if (andResume) { r.results = null; r.newIntents = null; + reportResumedActivityLocked(r); } return true; @@ -12225,19 +12879,21 @@ public final class ActivityManagerService extends ActivityManagerNative implemen */ private final boolean ensureActivityConfigurationLocked(HistoryRecord r, int globalChanges) { - if (DEBUG_SWITCH) Log.i(TAG, "Ensuring correct configuration: " + r); + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, + "Ensuring correct configuration: " + r); // Short circuit: if the two configurations are the exact same // object (the common case), then there is nothing to do. Configuration newConfig = mConfiguration; if (r.configuration == newConfig) { - if (DEBUG_SWITCH) Log.i(TAG, "Configuration unchanged in " + r); + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, + "Configuration unchanged in " + r); return true; } // We don't worry about activities that are finishing. if (r.finishing) { - if (DEBUG_SWITCH) Log.i(TAG, + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, "Configuration doesn't matter in finishing " + r); r.stopFreezingScreenLocked(false); return true; @@ -12251,7 +12907,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // If the activity isn't currently running, just leave the new // configuration and it will pick that up next time it starts. if (r.app == null || r.app.thread == null) { - if (DEBUG_SWITCH) Log.i(TAG, + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, "Configuration doesn't matter not running " + r); r.stopFreezingScreenLocked(false); return true; @@ -12263,22 +12919,26 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // Figure out what has changed between the two configurations. int changes = oldConfig.diff(newConfig); - if (DEBUG_SWITCH) { - Log.i(TAG, "Checking to restart " + r.info.name + ": changed=0x" + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) { + Log.v(TAG, "Checking to restart " + r.info.name + ": changed=0x" + Integer.toHexString(changes) + ", handles=0x" - + Integer.toHexString(r.info.configChanges)); + + Integer.toHexString(r.info.configChanges) + + ", newConfig=" + newConfig); } if ((changes&(~r.info.configChanges)) != 0) { // Aha, the activity isn't handling the change, so DIE DIE DIE. r.configChangeFlags |= changes; r.startFreezingScreenLocked(r.app, globalChanges); if (r.app == null || r.app.thread == null) { - if (DEBUG_SWITCH) Log.i(TAG, "Switch is destroying non-running " + r); + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, + "Switch is destroying non-running " + r); destroyActivityLocked(r, true); } else if (r.state == ActivityState.PAUSING) { // A little annoying: we are waiting for this activity to // finish pausing. Let's not do anything now, but just // flag that it needs to be restarted when done pausing. + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, + "Switch is skipping already pausing " + r); r.configDestroy = true; return true; } else if (r.state == ActivityState.RESUMED) { @@ -12286,11 +12946,13 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // and we need to restart the top, resumed activity. // Instead of doing the normal handshaking, just say // "restart!". - if (DEBUG_SWITCH) Log.i(TAG, "Switch is restarting resumed " + r); + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, + "Switch is restarting resumed " + r); relaunchActivityLocked(r, r.configChangeFlags, true); r.configChangeFlags = 0; } else { - if (DEBUG_SWITCH) Log.i(TAG, "Switch is restarting non-resumed " + r); + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Log.v(TAG, + "Switch is restarting non-resumed " + r); relaunchActivityLocked(r, r.configChangeFlags, false); r.configChangeFlags = 0; } @@ -12308,6 +12970,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen // it last got. if (r.app != null && r.app.thread != null) { try { + if (DEBUG_CONFIGURATION) Log.v(TAG, "Sending new config to " + r); r.app.thread.scheduleActivityConfigurationChanged(r); } catch (RemoteException e) { // If process died, whatever. @@ -12360,6 +13023,7 @@ public final class ActivityManagerService extends ActivityManagerNative implemen return (app.curAdj=app.maxAdj); } + app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN; app.adjSource = null; app.adjTarget = null; @@ -12440,15 +13104,6 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } if (app.services.size() != 0 && adj > FOREGROUND_APP_ADJ) { - // If this process has active services running in it, we would - // like to avoid killing it unless it would prevent the current - // application from running. By default we put the process in - // with the rest of the background processes; as we scan through - // its services we may bump it up from there. - if (adj > hiddenAdj) { - adj = hiddenAdj; - app.adjType = "bg-services"; - } final long now = SystemClock.uptimeMillis(); // This process is more important if the top activity is // bound to the service. @@ -12493,6 +13148,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen adj = clientAdj > VISIBLE_APP_ADJ ? clientAdj : VISIBLE_APP_ADJ; app.adjType = "service"; + app.adjTypeCode = ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE; app.adjSource = cr.binding.client; app.adjTarget = s.serviceInfo.name; } @@ -12506,23 +13163,27 @@ public final class ActivityManagerService extends ActivityManagerNative implemen || a.state == ActivityState.PAUSING)) { adj = FOREGROUND_APP_ADJ; app.adjType = "service"; + app.adjTypeCode = ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE; app.adjSource = a; app.adjTarget = s.serviceInfo.name; } } } } + + // Finally, f this process has active services running in it, we + // would like to avoid killing it unless it would prevent the current + // application from running. By default we put the process in + // with the rest of the background processes; as we scan through + // its services we may bump it up from there. + if (adj > hiddenAdj) { + adj = hiddenAdj; + app.adjType = "bg-services"; + } } if (app.pubProviders.size() != 0 && adj > FOREGROUND_APP_ADJ) { - // If this process has published any content providers, then - // its adjustment makes it at least as important as any of the - // processes using those providers, and no less important than - // CONTENT_PROVIDER_ADJ, which is just shy of EMPTY. - if (adj > CONTENT_PROVIDER_ADJ) { - adj = CONTENT_PROVIDER_ADJ; - app.adjType = "pub-providers"; - } Iterator jt = app.pubProviders.values().iterator(); while (jt.hasNext() && adj > FOREGROUND_APP_ADJ) { ContentProviderRecord cpr = (ContentProviderRecord)jt.next(); @@ -12548,6 +13209,8 @@ public final class ActivityManagerService extends ActivityManagerNative implemen adj = clientAdj > FOREGROUND_APP_ADJ ? clientAdj : FOREGROUND_APP_ADJ; app.adjType = "provider"; + app.adjTypeCode = ActivityManager.RunningAppProcessInfo + .REASON_PROVIDER_IN_USE; app.adjSource = client; app.adjTarget = cpr.info.name; } @@ -12564,6 +13227,15 @@ public final class ActivityManagerService extends ActivityManagerNative implemen } } } + + // Finally, if this process has published any content providers, + // then its adjustment makes it at least as important as any of the + // processes using those providers, and no less important than + // CONTENT_PROVIDER_ADJ, which is just shy of EMPTY. + if (adj > CONTENT_PROVIDER_ADJ) { + adj = CONTENT_PROVIDER_ADJ; + app.adjType = "pub-providers"; + } } app.curRawAdj = adj; diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index c834b34..d59aead 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -16,6 +16,7 @@ package com.android.server.am; +import android.bluetooth.BluetoothHeadset; import android.content.Context; import android.os.Binder; import android.os.IBinder; @@ -23,11 +24,11 @@ import android.os.Parcel; import android.os.Process; import android.os.ServiceManager; import android.telephony.SignalStrength; -import android.telephony.TelephonyManager; import android.util.Log; import com.android.internal.app.IBatteryStats; import com.android.internal.os.BatteryStatsImpl; +import com.android.internal.os.PowerProfile; import java.io.FileDescriptor; import java.io.PrintWriter; @@ -49,6 +50,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub { public void publish(Context context) { mContext = context; ServiceManager.addService("batteryinfo", asBinder()); + mStats.setNumSpeedSteps(new PowerProfile(mContext).getNumSpeedSteps()); + mStats.setRadioScanningTimeout(mContext.getResources().getInteger( + com.android.internal.R.integer.config_radioScanningTimeout) + * 1000L); } public void shutdown() { @@ -193,10 +198,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub { } } - public void noteAirplaneMode(boolean airplaneMode) { + public void notePhoneState(int state) { enforceCallingPermission(); synchronized (mStats) { - mStats.noteAirplaneModeLocked(airplaneMode); + mStats.notePhoneStateLocked(state); } } @@ -258,8 +263,10 @@ public final class BatteryStatsService extends IBatteryStats.Stub { public void noteBluetoothOn() { enforceCallingPermission(); + BluetoothHeadset headset = new BluetoothHeadset(mContext, null); synchronized (mStats) { mStats.noteBluetoothOnLocked(); + mStats.setBtHeadset(headset); } } diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java index da55049..db0a6cb 100644 --- a/services/java/com/android/server/am/BroadcastRecord.java +++ b/services/java/com/android/server/am/BroadcastRecord.java @@ -39,7 +39,9 @@ class BroadcastRecord extends Binder { final String callerPackage; // who sent this final int callingPid; // the pid of who sent this final int callingUid; // the uid of who sent this - String requiredPermission; // a permission the caller has required + final boolean ordered; // serialize the send to receivers? + final boolean sticky; // originated from existing sticky data? + final String requiredPermission; // a permission the caller has required final List receivers; // contains BroadcastFilter and ResolveInfo final IIntentReceiver resultTo; // who receives final result if non-null long dispatchTime; // when dispatch started on this set of receivers @@ -48,7 +50,6 @@ class BroadcastRecord extends Binder { String resultData; // current result data value. Bundle resultExtras; // current result extra data values. boolean resultAbort; // current result abortBroadcast value. - boolean ordered; // serialize the send to receivers? int nextReceiver; // next receiver to be executed. IBinder receiver; // who is currently running, null if none. int state; @@ -86,7 +87,7 @@ class BroadcastRecord extends Binder { + " resultCode=" + resultCode + " resultData=" + resultData); pw.println(prefix + "resultExtras=" + resultExtras); pw.println(prefix + "resultAbort=" + resultAbort - + " ordered=" + ordered); + + " ordered=" + ordered + " sticky=" + sticky); pw.println(prefix + "nextReceiver=" + nextReceiver + " receiver=" + receiver); pw.println(prefix + "curFilter=" + curFilter); @@ -122,7 +123,8 @@ class BroadcastRecord extends Binder { BroadcastRecord(Intent _intent, ProcessRecord _callerApp, String _callerPackage, int _callingPid, int _callingUid, String _requiredPermission, List _receivers, IIntentReceiver _resultTo, int _resultCode, - String _resultData, Bundle _resultExtras, boolean _serialized) { + String _resultData, Bundle _resultExtras, boolean _serialized, + boolean _sticky) { intent = _intent; callerApp = _callerApp; callerPackage = _callerPackage; @@ -135,6 +137,7 @@ class BroadcastRecord extends Binder { resultData = _resultData; resultExtras = _resultExtras; ordered = _serialized; + sticky = _sticky; nextReceiver = 0; state = IDLE; } diff --git a/services/java/com/android/server/am/ConnectionRecord.java b/services/java/com/android/server/am/ConnectionRecord.java index b3343dd..f613b00 100644 --- a/services/java/com/android/server/am/ConnectionRecord.java +++ b/services/java/com/android/server/am/ConnectionRecord.java @@ -17,6 +17,7 @@ package com.android.server.am; import android.app.IServiceConnection; +import android.app.PendingIntent; import java.io.PrintWriter; @@ -28,6 +29,8 @@ class ConnectionRecord { final HistoryRecord activity; // If non-null, the owning activity. final IServiceConnection conn; // The client connection. final int flags; // Binding options. + final int clientLabel; // String resource labeling this client. + final PendingIntent clientIntent; // How to launch the client. String stringName; // Caching of toString. void dump(PrintWriter pw, String prefix) { @@ -40,11 +43,14 @@ class ConnectionRecord { } ConnectionRecord(AppBindRecord _binding, HistoryRecord _activity, - IServiceConnection _conn, int _flags) { + IServiceConnection _conn, int _flags, + int _clientLabel, PendingIntent _clientIntent) { binding = _binding; activity = _activity; conn = _conn; flags = _flags; + clientLabel = _clientLabel; + clientIntent = _clientIntent; } public String toString() { diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index fa2a100..b3086d5 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -22,6 +22,7 @@ import android.content.IIntentReceiver; import android.app.PendingIntent; import android.content.Intent; import android.os.Binder; +import android.os.IBinder; import android.os.RemoteException; import android.util.Log; @@ -172,6 +173,14 @@ class PendingIntentRecord extends IIntentSender.Stub { public int send(int code, Intent intent, String resolvedType, IIntentReceiver finishedReceiver) { + return sendInner(code, intent, resolvedType, finishedReceiver, + null, null, 0, 0, 0); + } + + int sendInner(int code, Intent intent, String resolvedType, + IIntentReceiver finishedReceiver, + IBinder resultTo, String resultWho, int requestCode, + int flagsMask, int flagsValues) { synchronized(owner) { if (!canceled) { sent = true; @@ -189,6 +198,9 @@ class PendingIntentRecord extends IIntentSender.Stub { } else { resolvedType = key.requestResolvedType; } + flagsMask &= ~Intent.IMMUTABLE_FLAGS; + flagsValues &= flagsMask; + finalIntent.setFlags((finalIntent.getFlags()&~flagsMask) | flagsValues); final long origId = Binder.clearCallingIdentity(); @@ -198,7 +210,7 @@ class PendingIntentRecord extends IIntentSender.Stub { try { owner.startActivityInPackage(uid, finalIntent, resolvedType, - null, null, 0, false); + resultTo, resultWho, requestCode, false); } catch (RuntimeException e) { Log.w(ActivityManagerService.TAG, "Unable to send startActivity intent", e); @@ -236,7 +248,7 @@ class PendingIntentRecord extends IIntentSender.Stub { if (sendFinish) { try { finishedReceiver.performReceive(new Intent(finalIntent), 0, - null, null, false); + null, null, false, false); } catch (RemoteException e) { } } @@ -246,7 +258,7 @@ class PendingIntentRecord extends IIntentSender.Stub { return 0; } } - return -1; + return IActivityManager.START_CANCELED; } protected void finalize() throws Throwable { diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index 76fdf09..6202257 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -75,6 +75,7 @@ class ProcessRecord implements Watchdog.PssRequestor { boolean reportLowMemory; // Set to true when waiting to report low mem int lastPss; // Last pss size reported by app. String adjType; // Debugging: primary thing impacting oom_adj. + int adjTypeCode; // Debugging: adj code to report to app. Object adjSource; // Debugging: option dependent object. Object adjTarget; // Debugging: target component impacting oom_adj. @@ -93,7 +94,8 @@ class ProcessRecord implements Watchdog.PssRequestor { // class (String) -> ContentProviderRecord final HashMap pubProviders = new HashMap(); // All ContentProviderRecord process is using - final HashSet conProviders = new HashSet(); + final HashMap<ContentProviderRecord, Integer> conProviders + = new HashMap<ContentProviderRecord, Integer>(); boolean persistent; // always keep this application running? boolean crashing; // are we in the process of crashing? diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index 98df4b3..2534410 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -18,12 +18,16 @@ package com.android.server.am; import com.android.internal.os.BatteryStatsImpl; +import android.app.INotificationManager; +import android.app.Notification; +import android.app.NotificationManager; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.ServiceInfo; import android.os.Binder; import android.os.IBinder; +import android.os.RemoteException; import android.os.SystemClock; import java.io.PrintWriter; @@ -60,13 +64,38 @@ class ServiceRecord extends Binder { final HashMap<IBinder, ConnectionRecord> connections = new HashMap<IBinder, ConnectionRecord>(); // IBinder -> ConnectionRecord of all bound clients - final List<Intent> startArgs = new ArrayList<Intent>(); + + // Maximum number of delivery attempts before giving up. + static final int MAX_DELIVERY_COUNT = 3; + + // Maximum number of times it can fail during execution before giving up. + static final int MAX_DONE_EXECUTING_COUNT = 6; + + static class StartItem { + final int id; + final Intent intent; + long deliveredTime; + int deliveryCount; + int doneExecutingCount; + + StartItem(int _id, Intent _intent) { + id = _id; + intent = _intent; + } + } + final ArrayList<StartItem> deliveredStarts = new ArrayList<StartItem>(); + // start() arguments which been delivered. + final ArrayList<StartItem> pendingStarts = new ArrayList<StartItem>(); // start() arguments that haven't yet been delivered. - ProcessRecord app; // where this service is running or null. - boolean isForeground; // asked to run as a foreground service? + ProcessRecord app; // where this service is running or null. + boolean isForeground; // is service currently in foreground mode? + int foregroundId; // Notification ID of last foreground req. + Notification foregroundNoti; // Notification record of foreground state. long lastActivity; // last time there was some activity on the service. boolean startRequested; // someone explicitly called start? + boolean stopIfKilled; // last onStart() said to stop if service killed? + boolean callStart; // last onStart() has asked to alway be called on restart. int lastStartId; // identifier of most recent start request. int executeNesting; // number of outstanding operations keeping foreground. long executingStart; // start time of last execute request. @@ -79,6 +108,25 @@ class ServiceRecord extends Binder { String stringName; // caching of toString + void dumpStartList(PrintWriter pw, String prefix, List<StartItem> list, long now) { + final int N = list.size(); + for (int i=0; i<N; i++) { + StartItem si = list.get(i); + pw.print(prefix); pw.print("#"); pw.print(i); + pw.print(" id="); pw.print(si.id); + if (now != 0) pw.print(" dur="); pw.print(now-si.deliveredTime); + if (si.deliveryCount != 0) { + pw.print(" dc="); pw.print(si.deliveryCount); + } + if (si.doneExecutingCount != 0) { + pw.print(" dxc="); pw.print(si.doneExecutingCount); + } + pw.print(" "); + if (si.intent != null) pw.println(si.intent.toString()); + else pw.println("null"); + } + } + void dump(PrintWriter pw, String prefix) { pw.print(prefix); pw.print("intent={"); pw.print(intent.getIntent().toShortString(true, false)); @@ -93,20 +141,39 @@ class ServiceRecord extends Binder { if (!resDir.equals(baseDir)) pw.print(" resDir="); pw.print(resDir); pw.print(" dataDir="); pw.println(dataDir); pw.print(prefix); pw.print("app="); pw.println(app); - pw.print(prefix); pw.print("isForeground="); pw.print(isForeground); - pw.print(" lastActivity="); pw.println(lastActivity-now); - pw.print(prefix); pw.print("startRequested="); pw.print(startRequested); - pw.print(" startId="); pw.print(lastStartId); - pw.print(" executeNesting="); pw.print(executeNesting); + if (isForeground || foregroundId != 0) { + pw.print(prefix); pw.print("isForeground="); pw.print(isForeground); + pw.print(" foregroundId="); pw.print(foregroundId); + pw.print(" foregroundNoti="); pw.println(foregroundNoti); + } + pw.print(prefix); pw.print("lastActivity="); pw.print(lastActivity-now); pw.print(" executingStart="); pw.print(executingStart-now); - pw.print(" crashCount="); pw.println(crashCount); - pw.print(prefix); pw.print("totalRestartCount="); pw.print(totalRestartCount); - pw.print(" restartCount="); pw.print(restartCount); - pw.print(" restartDelay="); pw.print(restartDelay); - pw.print(" restartTime="); pw.print(restartTime-now); - pw.print(" nextRestartTime="); pw.println(nextRestartTime-now); + pw.print(" restartTime="); pw.println(restartTime); + if (startRequested || lastStartId != 0) { + pw.print(prefix); pw.print("startRequested="); pw.print(startRequested); + pw.print(" stopIfKilled="); pw.print(stopIfKilled); + pw.print(" callStart="); pw.print(callStart); + pw.print(" lastStartId="); pw.println(lastStartId); + } + if (executeNesting != 0 || crashCount != 0 || restartCount != 0 + || restartDelay != 0 || nextRestartTime != 0) { + pw.print(prefix); pw.print("executeNesting="); pw.print(executeNesting); + pw.print(" restartCount="); pw.print(restartCount); + pw.print(" restartDelay="); pw.print(restartDelay-now); + pw.print(" nextRestartTime="); pw.print(nextRestartTime-now); + pw.print(" crashCount="); pw.println(crashCount); + } + if (deliveredStarts.size() > 0) { + pw.print(prefix); pw.println("Delivered Starts:"); + dumpStartList(pw, prefix, deliveredStarts, SystemClock.uptimeMillis()); + } + if (pendingStarts.size() > 0) { + pw.print(prefix); pw.println("Pending Starts:"); + dumpStartList(pw, prefix, pendingStarts, 0); + } if (bindings.size() > 0) { Iterator<IntentBindRecord> it = bindings.values().iterator(); + pw.print(prefix); pw.println("Bindings:"); while (it.hasNext()) { IntentBindRecord b = it.next(); pw.print(prefix); pw.print("* IntentBindRecord{"); @@ -167,6 +234,45 @@ class ServiceRecord extends Binder { restartTime = 0; } + public StartItem findDeliveredStart(int id, boolean remove) { + final int N = deliveredStarts.size(); + for (int i=0; i<N; i++) { + StartItem si = deliveredStarts.get(i); + if (si.id == id) { + if (remove) deliveredStarts.remove(i); + return si; + } + } + + return null; + } + + public void postNotification() { + if (foregroundId != 0 && foregroundNoti != null) { + INotificationManager inm = NotificationManager.getService(); + if (inm != null) { + try { + int[] outId = new int[1]; + inm.enqueueNotification(packageName, foregroundId, + foregroundNoti, outId); + } catch (RemoteException e) { + } + } + } + } + + public void cancelNotification() { + if (foregroundId != 0) { + INotificationManager inm = NotificationManager.getService(); + if (inm != null) { + try { + inm.cancelNotification(packageName, foregroundId); + } catch (RemoteException e) { + } + } + } + } + public String toString() { if (stringName != null) { return stringName; diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java index d458911..373b44e 100755..100644 --- a/services/java/com/android/server/am/UsageStatsService.java +++ b/services/java/com/android/server/am/UsageStatsService.java @@ -381,7 +381,10 @@ public final class UsageStatsService extends IUsageStats.Stub { mFileLeaf = getCurrentDateStr(FILE_PREFIX); // Copy current file to back up File backupFile = new File(mFile.getPath() + ".bak"); - mFile.renameTo(backupFile); + if (!mFile.renameTo(backupFile)) { + Log.w(TAG, "Failed to persist new stats"); + return; + } try { // Write mStats to file writeStatsFLOCK(); @@ -695,7 +698,14 @@ public final class UsageStatsService extends IUsageStats.Stub { if (NC > 0) { for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) { sb.append("A:"); - sb.append(ent.getKey()); + String activity = ent.getKey(); + if (activity.startsWith(pkgName)) { + sb.append('*'); + sb.append(activity.substring( + pkgName.length(), activity.length())); + } else { + sb.append(activity); + } TimeStats times = ent.getValue(); sb.append(','); sb.append(times.count); |