diff options
Diffstat (limited to 'services/java')
187 files changed, 31362 insertions, 13096 deletions
diff --git a/services/java/Android.mk b/services/java/Android.mk index 95b28d981b6f..8c3d0f09197c 100644 --- a/services/java/Android.mk +++ b/services/java/Android.mk @@ -11,7 +11,7 @@ LOCAL_SRC_FILES := \ LOCAL_MODULE:= services -LOCAL_JAVA_LIBRARIES := android.policy telephony-common +LOCAL_JAVA_LIBRARIES := android.policy conscrypt telephony-common include $(BUILD_JAVA_LIBRARY) diff --git a/services/java/com/android/server/AlarmManagerService.java b/services/java/com/android/server/AlarmManagerService.java index fa758a88f412..3d804ef13829 100644 --- a/services/java/com/android/server/AlarmManagerService.java +++ b/services/java/com/android/server/AlarmManagerService.java @@ -38,11 +38,11 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.WorkSource; import android.text.TextUtils; -import android.text.format.Time; import android.util.Pair; import android.util.Slog; import android.util.TimeUtils; +import java.io.ByteArrayOutputStream; import java.io.FileDescriptor; import java.io.PrintWriter; import java.text.SimpleDateFormat; @@ -53,48 +53,55 @@ import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; -import java.util.Iterator; +import java.util.LinkedList; import java.util.Map; import java.util.TimeZone; +import static android.app.AlarmManager.RTC_WAKEUP; +import static android.app.AlarmManager.RTC; +import static android.app.AlarmManager.ELAPSED_REALTIME_WAKEUP; +import static android.app.AlarmManager.ELAPSED_REALTIME; + import com.android.internal.util.LocalLog; class AlarmManagerService extends IAlarmManager.Stub { // The threshold for how long an alarm can be late before we print a // warning message. The time duration is in milliseconds. private static final long LATE_ALARM_THRESHOLD = 10 * 1000; - - private static final int RTC_WAKEUP_MASK = 1 << AlarmManager.RTC_WAKEUP; - private static final int RTC_MASK = 1 << AlarmManager.RTC; - private static final int ELAPSED_REALTIME_WAKEUP_MASK = 1 << AlarmManager.ELAPSED_REALTIME_WAKEUP; - private static final int ELAPSED_REALTIME_MASK = 1 << AlarmManager.ELAPSED_REALTIME; + + private static final int RTC_WAKEUP_MASK = 1 << RTC_WAKEUP; + private static final int RTC_MASK = 1 << RTC; + private static final int ELAPSED_REALTIME_WAKEUP_MASK = 1 << ELAPSED_REALTIME_WAKEUP; + private static final int ELAPSED_REALTIME_MASK = 1 << ELAPSED_REALTIME; private static final int TIME_CHANGED_MASK = 1 << 16; + private static final int IS_WAKEUP_MASK = RTC_WAKEUP_MASK|ELAPSED_REALTIME_WAKEUP_MASK; - // Alignment quantum for inexact repeating alarms - private static final long QUANTUM = AlarmManager.INTERVAL_FIFTEEN_MINUTES; + // Mask for testing whether a given alarm type is wakeup vs non-wakeup + private static final int TYPE_NONWAKEUP_MASK = 0x1; // low bit => non-wakeup private static final String TAG = "AlarmManager"; private static final String ClockReceiver_TAG = "ClockReceiver"; private static final boolean localLOGV = false; + private static final boolean DEBUG_BATCH = localLOGV || false; + private static final boolean DEBUG_VALIDATE = localLOGV || false; private static final int ALARM_EVENT = 1; private static final String TIMEZONE_PROPERTY = "persist.sys.timezone"; private static final Intent mBackgroundIntent = new Intent().addFlags(Intent.FLAG_FROM_BACKGROUND); + private static final IncreasingTimeOrder sIncreasingTimeOrder = new IncreasingTimeOrder(); + private static final boolean WAKEUP_STATS = false; + private final Context mContext; private final LocalLog mLog = new LocalLog(TAG); private Object mLock = new Object(); - - private final ArrayList<Alarm> mRtcWakeupAlarms = new ArrayList<Alarm>(); - private final ArrayList<Alarm> mRtcAlarms = new ArrayList<Alarm>(); - private final ArrayList<Alarm> mElapsedRealtimeWakeupAlarms = new ArrayList<Alarm>(); - private final ArrayList<Alarm> mElapsedRealtimeAlarms = new ArrayList<Alarm>(); - private final IncreasingTimeOrder mIncreasingTimeOrder = new IncreasingTimeOrder(); - + private int mDescriptor; + private long mNextWakeup; + private long mNextNonWakeup; private int mBroadcastRefCount = 0; private PowerManager.WakeLock mWakeLock; private ArrayList<InFlight> mInFlight = new ArrayList<InFlight>(); @@ -106,14 +113,297 @@ class AlarmManagerService extends IAlarmManager.Stub { private final PendingIntent mTimeTickSender; private final PendingIntent mDateChangeSender; + class WakeupEvent { + public long when; + public int uid; + public String action; + + public WakeupEvent(long theTime, int theUid, String theAction) { + when = theTime; + uid = theUid; + action = theAction; + } + } + + private final LinkedList<WakeupEvent> mRecentWakeups = new LinkedList<WakeupEvent>(); + private final long RECENT_WAKEUP_PERIOD = 1000L * 60 * 60 * 24; // one day + + static final class Batch { + long start; // These endpoints are always in ELAPSED + long end; + boolean standalone; // certain "batches" don't participate in coalescing + + final ArrayList<Alarm> alarms = new ArrayList<Alarm>(); + + Batch() { + start = 0; + end = Long.MAX_VALUE; + } + + Batch(Alarm seed) { + start = seed.whenElapsed; + end = seed.maxWhen; + alarms.add(seed); + } + + int size() { + return alarms.size(); + } + + Alarm get(int index) { + return alarms.get(index); + } + + boolean canHold(long whenElapsed, long maxWhen) { + return (end >= whenElapsed) && (start <= maxWhen); + } + + boolean add(Alarm alarm) { + boolean newStart = false; + // narrows the batch if necessary; presumes that canHold(alarm) is true + int index = Collections.binarySearch(alarms, alarm, sIncreasingTimeOrder); + if (index < 0) { + index = 0 - index - 1; + } + alarms.add(index, alarm); + if (DEBUG_BATCH) { + Slog.v(TAG, "Adding " + alarm + " to " + this); + } + if (alarm.whenElapsed > start) { + start = alarm.whenElapsed; + newStart = true; + } + if (alarm.maxWhen < end) { + end = alarm.maxWhen; + } + + if (DEBUG_BATCH) { + Slog.v(TAG, " => now " + this); + } + return newStart; + } + + boolean remove(final PendingIntent operation) { + boolean didRemove = false; + long newStart = 0; // recalculate endpoints as we go + long newEnd = Long.MAX_VALUE; + for (int i = 0; i < alarms.size(); ) { + Alarm alarm = alarms.get(i); + if (alarm.operation.equals(operation)) { + alarms.remove(i); + didRemove = true; + } else { + if (alarm.whenElapsed > newStart) { + newStart = alarm.whenElapsed; + } + if (alarm.maxWhen < newEnd) { + newEnd = alarm.maxWhen; + } + i++; + } + } + if (didRemove) { + // commit the new batch bounds + start = newStart; + end = newEnd; + } + return didRemove; + } + + boolean remove(final String packageName) { + boolean didRemove = false; + long newStart = 0; // recalculate endpoints as we go + long newEnd = Long.MAX_VALUE; + for (int i = 0; i < alarms.size(); ) { + Alarm alarm = alarms.get(i); + if (alarm.operation.getTargetPackage().equals(packageName)) { + alarms.remove(i); + didRemove = true; + } else { + if (alarm.whenElapsed > newStart) { + newStart = alarm.whenElapsed; + } + if (alarm.maxWhen < newEnd) { + newEnd = alarm.maxWhen; + } + i++; + } + } + if (didRemove) { + // commit the new batch bounds + start = newStart; + end = newEnd; + } + return didRemove; + } + + boolean remove(final int userHandle) { + boolean didRemove = false; + long newStart = 0; // recalculate endpoints as we go + long newEnd = Long.MAX_VALUE; + for (int i = 0; i < alarms.size(); ) { + Alarm alarm = alarms.get(i); + if (UserHandle.getUserId(alarm.operation.getCreatorUid()) == userHandle) { + alarms.remove(i); + didRemove = true; + } else { + if (alarm.whenElapsed > newStart) { + newStart = alarm.whenElapsed; + } + if (alarm.maxWhen < newEnd) { + newEnd = alarm.maxWhen; + } + i++; + } + } + if (didRemove) { + // commit the new batch bounds + start = newStart; + end = newEnd; + } + return didRemove; + } + + boolean hasPackage(final String packageName) { + final int N = alarms.size(); + for (int i = 0; i < N; i++) { + Alarm a = alarms.get(i); + if (a.operation.getTargetPackage().equals(packageName)) { + return true; + } + } + return false; + } + + boolean hasWakeups() { + final int N = alarms.size(); + for (int i = 0; i < N; i++) { + Alarm a = alarms.get(i); + // non-wakeup alarms are types 1 and 3, i.e. have the low bit set + if ((a.type & TYPE_NONWAKEUP_MASK) == 0) { + return true; + } + } + return false; + } + + @Override + public String toString() { + StringBuilder b = new StringBuilder(40); + b.append("Batch{"); b.append(Integer.toHexString(this.hashCode())); + b.append(" num="); b.append(size()); + b.append(" start="); b.append(start); + b.append(" end="); b.append(end); + if (standalone) { + b.append(" STANDALONE"); + } + b.append('}'); + return b.toString(); + } + } + + static class BatchTimeOrder implements Comparator<Batch> { + public int compare(Batch b1, Batch b2) { + long when1 = b1.start; + long when2 = b2.start; + if (when1 - when2 > 0) { + return 1; + } + if (when1 - when2 < 0) { + return -1; + } + return 0; + } + } + + // minimum recurrence period or alarm futurity for us to be able to fuzz it + private static final long MIN_FUZZABLE_INTERVAL = 10000; + private static final BatchTimeOrder sBatchOrder = new BatchTimeOrder(); + private final ArrayList<Batch> mAlarmBatches = new ArrayList<Batch>(); + + static long convertToElapsed(long when, int type) { + final boolean isRtc = (type == RTC || type == RTC_WAKEUP); + if (isRtc) { + when -= System.currentTimeMillis() - SystemClock.elapsedRealtime(); + } + return when; + } + + // Apply a heuristic to { recurrence interval, futurity of the trigger time } to + // calculate the end of our nominal delivery window for the alarm. + static long maxTriggerTime(long now, long triggerAtTime, long interval) { + // Current heuristic: batchable window is 75% of either the recurrence interval + // [for a periodic alarm] or of the time from now to the desired delivery time, + // with a minimum delay/interval of 10 seconds, under which we will simply not + // defer the alarm. + long futurity = (interval == 0) + ? (triggerAtTime - now) + : interval; + if (futurity < MIN_FUZZABLE_INTERVAL) { + futurity = 0; + } + return triggerAtTime + (long)(.75 * futurity); + } + + // returns true if the batch was added at the head + static boolean addBatchLocked(ArrayList<Batch> list, Batch newBatch) { + int index = Collections.binarySearch(list, newBatch, sBatchOrder); + if (index < 0) { + index = 0 - index - 1; + } + list.add(index, newBatch); + return (index == 0); + } + + // Return the index of the matching batch, or -1 if none found. + int attemptCoalesceLocked(long whenElapsed, long maxWhen) { + final int N = mAlarmBatches.size(); + for (int i = 0; i < N; i++) { + Batch b = mAlarmBatches.get(i); + if (!b.standalone && b.canHold(whenElapsed, maxWhen)) { + return i; + } + } + return -1; + } + + // The RTC clock has moved arbitrarily, so we need to recalculate all the batching + void rebatchAllAlarms() { + synchronized (mLock) { + rebatchAllAlarmsLocked(true); + } + } + + void rebatchAllAlarmsLocked(boolean doValidate) { + ArrayList<Batch> oldSet = (ArrayList<Batch>) mAlarmBatches.clone(); + mAlarmBatches.clear(); + final long nowElapsed = SystemClock.elapsedRealtime(); + final int oldBatches = oldSet.size(); + for (int batchNum = 0; batchNum < oldBatches; batchNum++) { + Batch batch = oldSet.get(batchNum); + final int N = batch.size(); + for (int i = 0; i < N; i++) { + Alarm a = batch.get(i); + long whenElapsed = convertToElapsed(a.when, a.type); + long maxElapsed = (a.whenElapsed == a.maxWhen) + ? whenElapsed + : maxTriggerTime(nowElapsed, whenElapsed, a.repeatInterval); + setImplLocked(a.type, a.when, whenElapsed, maxElapsed, + a.repeatInterval, a.operation, batch.standalone, doValidate, a.workSource); + } + } + } + private static final class InFlight extends Intent { final PendingIntent mPendingIntent; + final WorkSource mWorkSource; final Pair<String, ComponentName> mTarget; final BroadcastStats mBroadcastStats; final FilterStats mFilterStats; - InFlight(AlarmManagerService service, PendingIntent pendingIntent) { + InFlight(AlarmManagerService service, PendingIntent pendingIntent, WorkSource workSource) { mPendingIntent = pendingIntent; + mWorkSource = workSource; Intent intent = pendingIntent.getIntent(); mTarget = intent != null ? new Pair<String, ComponentName>(intent.getAction(), intent.getComponent()) @@ -166,6 +456,7 @@ class AlarmManagerService extends IAlarmManager.Stub { public AlarmManagerService(Context context) { mContext = context; mDescriptor = init(); + mNextWakeup = mNextNonWakeup = 0; // We have to set current TimeZone info to kernel // because kernel doesn't keep this after reboot @@ -179,7 +470,8 @@ class AlarmManagerService extends IAlarmManager.Stub { mTimeTickSender = PendingIntent.getBroadcastAsUser(context, 0, new Intent(Intent.ACTION_TIME_TICK).addFlags( - Intent.FLAG_RECEIVER_REGISTERED_ONLY), 0, + Intent.FLAG_RECEIVER_REGISTERED_ONLY + | Intent.FLAG_RECEIVER_FOREGROUND), 0, UserHandle.ALL); Intent intent = new Intent(Intent.ACTION_DATE_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING); @@ -206,77 +498,170 @@ class AlarmManagerService extends IAlarmManager.Stub { super.finalize(); } } - - public void set(int type, long triggerAtTime, PendingIntent operation) { - setRepeating(type, triggerAtTime, 0, operation); + + @Override + public void set(int type, long triggerAtTime, long windowLength, long interval, + PendingIntent operation, WorkSource workSource) { + if (workSource != null) { + mContext.enforceCallingPermission( + android.Manifest.permission.UPDATE_DEVICE_STATS, + "AlarmManager.set"); + } + + set(type, triggerAtTime, windowLength, interval, operation, false, workSource); } - - public void setRepeating(int type, long triggerAtTime, long interval, - PendingIntent operation) { + + public void set(int type, long triggerAtTime, long windowLength, long interval, + PendingIntent operation, boolean isStandalone, WorkSource workSource) { if (operation == null) { Slog.w(TAG, "set/setRepeating ignored because there is no intent"); return; } - synchronized (mLock) { - Alarm alarm = new Alarm(); - alarm.type = type; - alarm.when = triggerAtTime; - alarm.repeatInterval = interval; - alarm.operation = operation; - // Remove this alarm if already scheduled. - removeLocked(operation); + // Sanity check the window length. This will catch people mistakenly + // trying to pass an end-of-window timestamp rather than a duration. + if (windowLength > AlarmManager.INTERVAL_HALF_DAY) { + Slog.w(TAG, "Window length " + windowLength + + "ms suspiciously long; limiting to 1 hour"); + windowLength = AlarmManager.INTERVAL_HOUR; + } + + if (type < RTC_WAKEUP || type > ELAPSED_REALTIME) { + throw new IllegalArgumentException("Invalid alarm type " + type); + } + + if (triggerAtTime < 0) { + final long who = Binder.getCallingUid(); + final long what = Binder.getCallingPid(); + Slog.w(TAG, "Invalid alarm trigger time! " + triggerAtTime + " from uid=" + who + + " pid=" + what); + triggerAtTime = 0; + } - if (localLOGV) Slog.v(TAG, "set: " + alarm); + final long nowElapsed = SystemClock.elapsedRealtime(); + final long triggerElapsed = convertToElapsed(triggerAtTime, type); + final long maxElapsed; + if (windowLength == AlarmManager.WINDOW_EXACT) { + maxElapsed = triggerElapsed; + } else if (windowLength < 0) { + maxElapsed = maxTriggerTime(nowElapsed, triggerElapsed, interval); + } else { + maxElapsed = triggerElapsed + windowLength; + } - int index = addAlarmLocked(alarm); - if (index == 0) { - setLocked(alarm); + synchronized (mLock) { + if (DEBUG_BATCH) { + Slog.v(TAG, "set(" + operation + ") : type=" + type + + " triggerAtTime=" + triggerAtTime + " win=" + windowLength + + " tElapsed=" + triggerElapsed + " maxElapsed=" + maxElapsed + + " interval=" + interval + " standalone=" + isStandalone); } + setImplLocked(type, triggerAtTime, triggerElapsed, maxElapsed, + interval, operation, isStandalone, true, workSource); } } - - public void setInexactRepeating(int type, long triggerAtTime, long interval, - PendingIntent operation) { - if (operation == null) { - Slog.w(TAG, "setInexactRepeating ignored because there is no intent"); - return; + + private void setImplLocked(int type, long when, long whenElapsed, long maxWhen, long interval, + PendingIntent operation, boolean isStandalone, boolean doValidate, + WorkSource workSource) { + Alarm a = new Alarm(type, when, whenElapsed, maxWhen, interval, operation, workSource); + removeLocked(operation); + + boolean reschedule; + int whichBatch = (isStandalone) ? -1 : attemptCoalesceLocked(whenElapsed, maxWhen); + if (whichBatch < 0) { + Batch batch = new Batch(a); + batch.standalone = isStandalone; + reschedule = addBatchLocked(mAlarmBatches, batch); + } else { + Batch batch = mAlarmBatches.get(whichBatch); + reschedule = batch.add(a); + if (reschedule) { + // The start time of this batch advanced, so batch ordering may + // have just been broken. Move it to where it now belongs. + mAlarmBatches.remove(whichBatch); + addBatchLocked(mAlarmBatches, batch); + } } - if (interval <= 0) { - Slog.w(TAG, "setInexactRepeating ignored because interval " + interval - + " is invalid"); - return; + if (DEBUG_VALIDATE) { + if (doValidate && !validateConsistencyLocked()) { + Slog.v(TAG, "Tipping-point operation: type=" + type + " when=" + when + + " when(hex)=" + Long.toHexString(when) + + " whenElapsed=" + whenElapsed + " maxWhen=" + maxWhen + + " interval=" + interval + " op=" + operation + + " standalone=" + isStandalone); + rebatchAllAlarmsLocked(false); + reschedule = true; + } } - // If the requested interval isn't a multiple of 15 minutes, just treat it as exact - if (interval % QUANTUM != 0) { - if (localLOGV) Slog.v(TAG, "Interval " + interval + " not a quantum multiple"); - setRepeating(type, triggerAtTime, interval, operation); - return; + if (reschedule) { + rescheduleKernelAlarmsLocked(); } + } - // Translate times into the ELAPSED timebase for alignment purposes so that - // alignment never tries to match against wall clock times. - final boolean isRtc = (type == AlarmManager.RTC || type == AlarmManager.RTC_WAKEUP); - final long skew = (isRtc) - ? System.currentTimeMillis() - SystemClock.elapsedRealtime() - : 0; + private void logBatchesLocked() { + ByteArrayOutputStream bs = new ByteArrayOutputStream(2048); + PrintWriter pw = new PrintWriter(bs); + final long nowRTC = System.currentTimeMillis(); + final long nowELAPSED = SystemClock.elapsedRealtime(); + final int NZ = mAlarmBatches.size(); + for (int iz = 0; iz < NZ; iz++) { + Batch bz = mAlarmBatches.get(iz); + pw.append("Batch "); pw.print(iz); pw.append(": "); pw.println(bz); + dumpAlarmList(pw, bz.alarms, " ", nowELAPSED, nowRTC); + pw.flush(); + Slog.v(TAG, bs.toString()); + bs.reset(); + } + } - // Slip forward to the next ELAPSED-timebase quantum after the stated time. If - // we're *at* a quantum point, leave it alone. - final long adjustedTriggerTime; - long offset = (triggerAtTime - skew) % QUANTUM; - if (offset != 0) { - adjustedTriggerTime = triggerAtTime - offset + QUANTUM; - } else { - adjustedTriggerTime = triggerAtTime; + private boolean validateConsistencyLocked() { + if (DEBUG_VALIDATE) { + long lastTime = Long.MIN_VALUE; + final int N = mAlarmBatches.size(); + for (int i = 0; i < N; i++) { + Batch b = mAlarmBatches.get(i); + if (b.start >= lastTime) { + // duplicate start times are okay because of standalone batches + lastTime = b.start; + } else { + Slog.e(TAG, "CONSISTENCY FAILURE: Batch " + i + " is out of order"); + logBatchesLocked(); + return false; + } + } } + return true; + } - // Set the alarm based on the quantum-aligned start time - if (localLOGV) Slog.v(TAG, "setInexactRepeating: type=" + type + " interval=" + interval - + " trigger=" + adjustedTriggerTime + " orig=" + triggerAtTime); - setRepeating(type, adjustedTriggerTime, interval, operation); + private Batch findFirstWakeupBatchLocked() { + final int N = mAlarmBatches.size(); + for (int i = 0; i < N; i++) { + Batch b = mAlarmBatches.get(i); + if (b.hasWakeups()) { + return b; + } + } + return null; + } + + private void rescheduleKernelAlarmsLocked() { + // Schedule the next upcoming wakeup alarm. If there is a deliverable batch + // prior to that which contains no wakeups, we schedule that as well. + if (mAlarmBatches.size() > 0) { + final Batch firstWakeup = findFirstWakeupBatchLocked(); + final Batch firstBatch = mAlarmBatches.get(0); + if (firstWakeup != null && mNextWakeup != firstWakeup.start) { + mNextWakeup = firstWakeup.start; + setLocked(ELAPSED_REALTIME_WAKEUP, firstWakeup.start); + } + if (firstBatch != firstWakeup && mNextNonWakeup != firstBatch.start) { + mNextNonWakeup = firstBatch.start; + setLocked(ELAPSED_REALTIME, firstBatch.start); + } + } } public void setTime(long millis) { @@ -338,163 +723,88 @@ class AlarmManagerService extends IAlarmManager.Stub { } public void removeLocked(PendingIntent operation) { - removeLocked(mRtcWakeupAlarms, operation); - removeLocked(mRtcAlarms, operation); - removeLocked(mElapsedRealtimeWakeupAlarms, operation); - removeLocked(mElapsedRealtimeAlarms, operation); - } - - private void removeLocked(ArrayList<Alarm> alarmList, - PendingIntent operation) { - if (alarmList.size() <= 0) { - return; + boolean didRemove = false; + for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { + Batch b = mAlarmBatches.get(i); + didRemove |= b.remove(operation); + if (b.size() == 0) { + mAlarmBatches.remove(i); + } } - // iterator over the list removing any it where the intent match - Iterator<Alarm> it = alarmList.iterator(); - - while (it.hasNext()) { - Alarm alarm = it.next(); - if (alarm.operation.equals(operation)) { - it.remove(); + if (didRemove) { + if (DEBUG_BATCH) { + Slog.v(TAG, "remove(operation) changed bounds; rebatching"); } + rebatchAllAlarmsLocked(true); + rescheduleKernelAlarmsLocked(); } } public void removeLocked(String packageName) { - removeLocked(mRtcWakeupAlarms, packageName); - removeLocked(mRtcAlarms, packageName); - removeLocked(mElapsedRealtimeWakeupAlarms, packageName); - removeLocked(mElapsedRealtimeAlarms, packageName); - } - - private void removeLocked(ArrayList<Alarm> alarmList, - String packageName) { - if (alarmList.size() <= 0) { - return; + boolean didRemove = false; + for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { + Batch b = mAlarmBatches.get(i); + didRemove |= b.remove(packageName); + if (b.size() == 0) { + mAlarmBatches.remove(i); + } } - // iterator over the list removing any it where the intent match - Iterator<Alarm> it = alarmList.iterator(); - - while (it.hasNext()) { - Alarm alarm = it.next(); - if (alarm.operation.getTargetPackage().equals(packageName)) { - it.remove(); + if (didRemove) { + if (DEBUG_BATCH) { + Slog.v(TAG, "remove(package) changed bounds; rebatching"); } + rebatchAllAlarmsLocked(true); + rescheduleKernelAlarmsLocked(); } } public void removeUserLocked(int userHandle) { - removeUserLocked(mRtcWakeupAlarms, userHandle); - removeUserLocked(mRtcAlarms, userHandle); - removeUserLocked(mElapsedRealtimeWakeupAlarms, userHandle); - removeUserLocked(mElapsedRealtimeAlarms, userHandle); - } - - private void removeUserLocked(ArrayList<Alarm> alarmList, int userHandle) { - if (alarmList.size() <= 0) { - return; + boolean didRemove = false; + for (int i = mAlarmBatches.size() - 1; i >= 0; i--) { + Batch b = mAlarmBatches.get(i); + didRemove |= b.remove(userHandle); + if (b.size() == 0) { + mAlarmBatches.remove(i); + } } - // iterator over the list removing any it where the intent match - Iterator<Alarm> it = alarmList.iterator(); - - while (it.hasNext()) { - Alarm alarm = it.next(); - if (UserHandle.getUserId(alarm.operation.getCreatorUid()) == userHandle) { - it.remove(); + if (didRemove) { + if (DEBUG_BATCH) { + Slog.v(TAG, "remove(user) changed bounds; rebatching"); } + rebatchAllAlarmsLocked(true); + rescheduleKernelAlarmsLocked(); } } - - public boolean lookForPackageLocked(String packageName) { - return lookForPackageLocked(mRtcWakeupAlarms, packageName) - || lookForPackageLocked(mRtcAlarms, packageName) - || lookForPackageLocked(mElapsedRealtimeWakeupAlarms, packageName) - || lookForPackageLocked(mElapsedRealtimeAlarms, packageName); - } - private boolean lookForPackageLocked(ArrayList<Alarm> alarmList, String packageName) { - for (int i=alarmList.size()-1; i>=0; i--) { - if (alarmList.get(i).operation.getTargetPackage().equals(packageName)) { + public boolean lookForPackageLocked(String packageName) { + for (int i = 0; i < mAlarmBatches.size(); i++) { + Batch b = mAlarmBatches.get(i); + if (b.hasPackage(packageName)) { return true; } } return false; } - - private ArrayList<Alarm> getAlarmList(int type) { - switch (type) { - case AlarmManager.RTC_WAKEUP: return mRtcWakeupAlarms; - case AlarmManager.RTC: return mRtcAlarms; - case AlarmManager.ELAPSED_REALTIME_WAKEUP: return mElapsedRealtimeWakeupAlarms; - case AlarmManager.ELAPSED_REALTIME: return mElapsedRealtimeAlarms; - } - - return null; - } - - private int addAlarmLocked(Alarm alarm) { - ArrayList<Alarm> alarmList = getAlarmList(alarm.type); - - int index = Collections.binarySearch(alarmList, alarm, mIncreasingTimeOrder); - if (index < 0) { - index = 0 - index - 1; - } - if (localLOGV) Slog.v(TAG, "Adding alarm " + alarm + " at " + index); - alarmList.add(index, alarm); - - if (localLOGV) { - // Display the list of alarms for this alarm type - Slog.v(TAG, "alarms: " + alarmList.size() + " type: " + alarm.type); - int position = 0; - for (Alarm a : alarmList) { - Time time = new Time(); - time.set(a.when); - String timeStr = time.format("%b %d %I:%M:%S %p"); - Slog.v(TAG, position + ": " + timeStr - + " " + a.operation.getTargetPackage()); - position += 1; - } - } - - return index; - } - - public long timeToNextAlarm() { - long nextAlarm = Long.MAX_VALUE; - synchronized (mLock) { - for (int i=AlarmManager.RTC_WAKEUP; - i<=AlarmManager.ELAPSED_REALTIME; i++) { - ArrayList<Alarm> alarmList = getAlarmList(i); - if (alarmList.size() > 0) { - Alarm a = alarmList.get(0); - if (a.when < nextAlarm) { - nextAlarm = a.when; - } - } - } - } - return nextAlarm; - } - - private void setLocked(Alarm alarm) + + private void setLocked(int type, long when) { if (mDescriptor != -1) { // The kernel never triggers alarms with negative wakeup times // so we ensure they are positive. long alarmSeconds, alarmNanoseconds; - if (alarm.when < 0) { + if (when < 0) { alarmSeconds = 0; alarmNanoseconds = 0; } else { - alarmSeconds = alarm.when / 1000; - alarmNanoseconds = (alarm.when % 1000) * 1000 * 1000; + alarmSeconds = when / 1000; + alarmNanoseconds = (when % 1000) * 1000 * 1000; } - set(mDescriptor, alarm.type, alarmSeconds, alarmNanoseconds); + set(mDescriptor, type, alarmSeconds, alarmNanoseconds); } else { @@ -502,7 +812,7 @@ class AlarmManagerService extends IAlarmManager.Stub { msg.what = ALARM_EVENT; mHandler.removeMessages(ALARM_EVENT); - mHandler.sendMessageAtTime(msg, alarm.when); + mHandler.sendMessageAtTime(msg, when); } } @@ -518,29 +828,28 @@ class AlarmManagerService extends IAlarmManager.Stub { synchronized (mLock) { pw.println("Current Alarm Manager state:"); - if (mRtcWakeupAlarms.size() > 0 || mRtcAlarms.size() > 0) { - final long now = System.currentTimeMillis(); - SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); - pw.println(" "); - pw.print(" Realtime wakeup (now="); - pw.print(sdf.format(new Date(now))); pw.println("):"); - if (mRtcWakeupAlarms.size() > 0) { - dumpAlarmList(pw, mRtcWakeupAlarms, " ", "RTC_WAKEUP", now); - } - if (mRtcAlarms.size() > 0) { - dumpAlarmList(pw, mRtcAlarms, " ", "RTC", now); - } - } - if (mElapsedRealtimeWakeupAlarms.size() > 0 || mElapsedRealtimeAlarms.size() > 0) { - final long now = SystemClock.elapsedRealtime(); - pw.println(" "); - pw.print(" Elapsed realtime wakeup (now="); - TimeUtils.formatDuration(now, pw); pw.println("):"); - if (mElapsedRealtimeWakeupAlarms.size() > 0) { - dumpAlarmList(pw, mElapsedRealtimeWakeupAlarms, " ", "ELAPSED_WAKEUP", now); - } - if (mElapsedRealtimeAlarms.size() > 0) { - dumpAlarmList(pw, mElapsedRealtimeAlarms, " ", "ELAPSED", now); + final long nowRTC = System.currentTimeMillis(); + final long nowELAPSED = SystemClock.elapsedRealtime(); + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + + pw.print("nowRTC="); pw.print(nowRTC); + pw.print("="); pw.print(sdf.format(new Date(nowRTC))); + pw.print(" nowELAPSED="); pw.println(nowELAPSED); + + long nextWakeupRTC = mNextWakeup + (nowRTC - nowELAPSED); + long nextNonWakeupRTC = mNextNonWakeup + (nowRTC - nowELAPSED); + pw.print("Next alarm: "); pw.print(mNextNonWakeup); + pw.print(" = "); pw.println(sdf.format(new Date(nextNonWakeupRTC))); + pw.print("Next wakeup: "); pw.print(mNextWakeup); + pw.print(" = "); pw.println(sdf.format(new Date(nextWakeupRTC))); + + if (mAlarmBatches.size() > 0) { + pw.println(); + pw.print("Pending alarm batches: "); + pw.println(mAlarmBatches.size()); + for (Batch b : mAlarmBatches) { + pw.print(b); pw.println(':'); + dumpAlarmList(pw, b.alarms, " ", nowELAPSED, nowRTC); } } @@ -643,6 +952,26 @@ class AlarmManagerService extends IAlarmManager.Stub { pw.println(); } } + + if (WAKEUP_STATS) { + pw.println(); + pw.println(" Recent Wakeup History:"); + long last = -1; + for (WakeupEvent event : mRecentWakeups) { + pw.print(" "); pw.print(sdf.format(new Date(event.when))); + pw.print('|'); + if (last < 0) { + pw.print('0'); + } else { + pw.print(event.when - last); + } + last = event.when; + pw.print('|'); pw.print(event.uid); + pw.print('|'); pw.print(event.action); + pw.println(); + } + pw.println(); + } } } @@ -655,74 +984,78 @@ class AlarmManagerService extends IAlarmManager.Stub { a.dump(pw, prefix + " ", now); } } - + + private static final String labelForType(int type) { + switch (type) { + case RTC: return "RTC"; + case RTC_WAKEUP : return "RTC_WAKEUP"; + case ELAPSED_REALTIME : return "ELAPSED"; + case ELAPSED_REALTIME_WAKEUP: return "ELAPSED_WAKEUP"; + default: + break; + } + return "--unknown--"; + } + + private static final void dumpAlarmList(PrintWriter pw, ArrayList<Alarm> list, + String prefix, long nowELAPSED, long nowRTC) { + for (int i=list.size()-1; i>=0; i--) { + Alarm a = list.get(i); + final String label = labelForType(a.type); + long now = (a.type <= RTC) ? nowRTC : nowELAPSED; + pw.print(prefix); pw.print(label); pw.print(" #"); pw.print(i); + pw.print(": "); pw.println(a); + a.dump(pw, prefix + " ", now); + } + } + private native int init(); private native void close(int fd); private native void set(int fd, int type, long seconds, long nanoseconds); private native int waitForAlarm(int fd); private native int setKernelTimezone(int fd, int minuteswest); - private void triggerAlarmsLocked(ArrayList<Alarm> alarmList, - ArrayList<Alarm> triggerList, - long now) - { - Iterator<Alarm> it = alarmList.iterator(); - ArrayList<Alarm> repeats = new ArrayList<Alarm>(); - - while (it.hasNext()) - { - Alarm alarm = it.next(); - - if (localLOGV) Slog.v(TAG, "Checking active alarm when=" + alarm.when + " " + alarm); - - if (alarm.when > now) { - // don't fire alarms in the future + private void triggerAlarmsLocked(ArrayList<Alarm> triggerList, long nowELAPSED, long nowRTC) { + // batches are temporally sorted, so we need only pull from the + // start of the list until we either empty it or hit a batch + // that is not yet deliverable + while (mAlarmBatches.size() > 0) { + Batch batch = mAlarmBatches.get(0); + if (batch.start > nowELAPSED) { + // Everything else is scheduled for the future break; } - - // If the alarm is late, then print a warning message. - // Note that this can happen if the user creates a new event on - // the Calendar app with a reminder that is in the past. In that - // case, the reminder alarm will fire immediately. - if (localLOGV && now - alarm.when > LATE_ALARM_THRESHOLD) { - Slog.v(TAG, "alarm is late! alarm time: " + alarm.when - + " now: " + now + " delay (in seconds): " - + (now - alarm.when) / 1000); - } - // Recurring alarms may have passed several alarm intervals while the - // phone was asleep or off, so pass a trigger count when sending them. - if (localLOGV) Slog.v(TAG, "Alarm triggering: " + alarm); - alarm.count = 1; - if (alarm.repeatInterval > 0) { - // this adjustment will be zero if we're late by - // less than one full repeat interval - alarm.count += (now - alarm.when) / alarm.repeatInterval; - } - triggerList.add(alarm); - - // remove the alarm from the list - it.remove(); - - // if it repeats queue it up to be read-added to the list - if (alarm.repeatInterval > 0) { - repeats.add(alarm); - } - } + // We will (re)schedule some alarms now; don't let that interfere + // with delivery of this current batch + mAlarmBatches.remove(0); - // reset any repeating alarms. - it = repeats.iterator(); - while (it.hasNext()) { - Alarm alarm = it.next(); - alarm.when += alarm.count * alarm.repeatInterval; - addAlarmLocked(alarm); - } - - if (alarmList.size() > 0) { - setLocked(alarmList.get(0)); + final int N = batch.size(); + for (int i = 0; i < N; i++) { + Alarm alarm = batch.get(i); + alarm.count = 1; + triggerList.add(alarm); + + // Recurring alarms may have passed several alarm intervals while the + // phone was asleep or off, so pass a trigger count when sending them. + if (alarm.repeatInterval > 0) { + // this adjustment will be zero if we're late by + // less than one full repeat interval + alarm.count += (nowELAPSED - alarm.whenElapsed) / alarm.repeatInterval; + + // Also schedule its next recurrence + final long delta = alarm.count * alarm.repeatInterval; + final long nextElapsed = alarm.whenElapsed + delta; + setImplLocked(alarm.type, alarm.when + delta, nextElapsed, + maxTriggerTime(nowELAPSED, nextElapsed, alarm.repeatInterval), + alarm.repeatInterval, alarm.operation, batch.standalone, true, + alarm.workSource); + } + + } } } - + /** * This Comparator sorts Alarms into increasing time order. */ @@ -744,15 +1077,23 @@ class AlarmManagerService extends IAlarmManager.Stub { public int type; public int count; public long when; + public long whenElapsed; // 'when' in the elapsed time base + public long maxWhen; // also in the elapsed time base public long repeatInterval; public PendingIntent operation; + public WorkSource workSource; - public Alarm() { - when = 0; - repeatInterval = 0; - operation = null; + public Alarm(int _type, long _when, long _whenElapsed, long _maxWhen, + long _interval, PendingIntent _op, WorkSource _ws) { + type = _type; + when = _when; + whenElapsed = _whenElapsed; + maxWhen = _maxWhen; + repeatInterval = _interval; + operation = _op; + workSource = _ws; } - + @Override public String toString() { @@ -769,13 +1110,33 @@ class AlarmManagerService extends IAlarmManager.Stub { public void dump(PrintWriter pw, String prefix, long now) { pw.print(prefix); pw.print("type="); pw.print(type); + pw.print(" whenElapsed="); pw.print(whenElapsed); pw.print(" when="); TimeUtils.formatDuration(when, now, pw); pw.print(" repeatInterval="); pw.print(repeatInterval); pw.print(" count="); pw.println(count); pw.print(prefix); pw.print("operation="); pw.println(operation); } } - + + void recordWakeupAlarms(ArrayList<Batch> batches, long nowELAPSED, long nowRTC) { + final int numBatches = batches.size(); + for (int nextBatch = 0; nextBatch < numBatches; nextBatch++) { + Batch b = batches.get(nextBatch); + if (b.start > nowELAPSED) { + break; + } + + final int numAlarms = b.alarms.size(); + for (int nextAlarm = 0; nextAlarm < numAlarms; nextAlarm++) { + Alarm a = b.alarms.get(nextAlarm); + WakeupEvent e = new WakeupEvent(nowRTC, + a.operation.getCreatorUid(), + a.operation.getIntent().getAction()); + mRecentWakeups.add(e); + } + } + } + private class AlarmThread extends Thread { public AlarmThread() @@ -785,14 +1146,20 @@ class AlarmManagerService extends IAlarmManager.Stub { public void run() { + ArrayList<Alarm> triggerList = new ArrayList<Alarm>(); + while (true) { int result = waitForAlarm(mDescriptor); - - ArrayList<Alarm> triggerList = new ArrayList<Alarm>(); - + + triggerList.clear(); + if ((result & TIME_CHANGED_MASK) != 0) { + if (DEBUG_BATCH) { + Slog.v(TAG, "Time changed notification from kernel; rebatching"); + } remove(mTimeTickSender); + rebatchAllAlarms(); mClockReceiver.scheduleTimeTickEvent(); Intent intent = new Intent(Intent.ACTION_TIME_CHANGED); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING @@ -807,22 +1174,28 @@ class AlarmManagerService extends IAlarmManager.Stub { TAG, "Checking for alarms... rtc=" + nowRTC + ", elapsed=" + nowELAPSED); - if ((result & RTC_WAKEUP_MASK) != 0) - triggerAlarmsLocked(mRtcWakeupAlarms, triggerList, nowRTC); - - if ((result & RTC_MASK) != 0) - triggerAlarmsLocked(mRtcAlarms, triggerList, nowRTC); - - if ((result & ELAPSED_REALTIME_WAKEUP_MASK) != 0) - triggerAlarmsLocked(mElapsedRealtimeWakeupAlarms, triggerList, nowELAPSED); - - if ((result & ELAPSED_REALTIME_MASK) != 0) - triggerAlarmsLocked(mElapsedRealtimeAlarms, triggerList, nowELAPSED); - - // now trigger the alarms - Iterator<Alarm> it = triggerList.iterator(); - while (it.hasNext()) { - Alarm alarm = it.next(); + if (WAKEUP_STATS) { + if ((result & IS_WAKEUP_MASK) != 0) { + long newEarliest = nowRTC - RECENT_WAKEUP_PERIOD; + int n = 0; + for (WakeupEvent event : mRecentWakeups) { + if (event.when > newEarliest) break; + n++; // number of now-stale entries at the list head + } + for (int i = 0; i < n; i++) { + mRecentWakeups.remove(); + } + + recordWakeupAlarms(mAlarmBatches, nowELAPSED, nowRTC); + } + } + + triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC); + rescheduleKernelAlarmsLocked(); + + // now deliver the alarm intents + for (int i=0; i<triggerList.size(); i++) { + Alarm alarm = triggerList.get(i); try { if (localLOGV) Slog.v(TAG, "sending alarm " + alarm); alarm.operation.send(mContext, 0, @@ -832,11 +1205,11 @@ class AlarmManagerService extends IAlarmManager.Stub { // we have an active broadcast so stay awake. if (mBroadcastRefCount == 0) { - setWakelockWorkSource(alarm.operation); + setWakelockWorkSource(alarm.operation, alarm.workSource); mWakeLock.acquire(); } final InFlight inflight = new InFlight(AlarmManagerService.this, - alarm.operation); + alarm.operation, alarm.workSource); mInFlight.add(inflight); mBroadcastRefCount++; @@ -856,8 +1229,8 @@ class AlarmManagerService extends IAlarmManager.Stub { } else { fs.nesting++; } - if (alarm.type == AlarmManager.ELAPSED_REALTIME_WAKEUP - || alarm.type == AlarmManager.RTC_WAKEUP) { + if (alarm.type == ELAPSED_REALTIME_WAKEUP + || alarm.type == RTC_WAKEUP) { bs.numWakeup++; fs.numWakeup++; ActivityManagerNative.noteWakeupAlarm( @@ -878,8 +1251,18 @@ class AlarmManagerService extends IAlarmManager.Stub { } } - void setWakelockWorkSource(PendingIntent pi) { + /** + * Attribute blame for a WakeLock. + * @param pi PendingIntent to attribute blame to if ws is null. + * @param ws WorkSource to attribute blame. + */ + void setWakelockWorkSource(PendingIntent pi, WorkSource ws) { try { + if (ws != null) { + mWakeLock.setWorkSource(ws); + return; + } + final int uid = ActivityManagerNative.getDefault() .getUidForIntentSender(pi.getTarget()); if (uid >= 0) { @@ -906,17 +1289,13 @@ class AlarmManagerService extends IAlarmManager.Stub { ArrayList<Alarm> triggerList = new ArrayList<Alarm>(); synchronized (mLock) { final long nowRTC = System.currentTimeMillis(); - triggerAlarmsLocked(mRtcWakeupAlarms, triggerList, nowRTC); - triggerAlarmsLocked(mRtcAlarms, triggerList, nowRTC); - triggerAlarmsLocked(mElapsedRealtimeWakeupAlarms, triggerList, nowRTC); - triggerAlarmsLocked(mElapsedRealtimeAlarms, triggerList, nowRTC); + final long nowELAPSED = SystemClock.elapsedRealtime(); + triggerAlarmsLocked(triggerList, nowELAPSED, nowRTC); } // now trigger the alarms without the lock held - Iterator<Alarm> it = triggerList.iterator(); - while (it.hasNext()) - { - Alarm alarm = it.next(); + for (int i=0; i<triggerList.size(); i++) { + Alarm alarm = triggerList.get(i); try { alarm.operation.send(); } catch (PendingIntent.CanceledException e) { @@ -942,7 +1321,10 @@ class AlarmManagerService extends IAlarmManager.Stub { @Override public void onReceive(Context context, Intent intent) { if (intent.getAction().equals(Intent.ACTION_TIME_TICK)) { - scheduleTimeTickEvent(); + if (DEBUG_BATCH) { + Slog.v(TAG, "Received TIME_TICK alarm; rescheduling"); + } + scheduleTimeTickEvent(); } else if (intent.getAction().equals(Intent.ACTION_DATE_CHANGED)) { // Since the kernel does not keep track of DST, we need to // reset the TZ information at the beginning of each day @@ -951,7 +1333,7 @@ class AlarmManagerService extends IAlarmManager.Stub { TimeZone zone = TimeZone.getTimeZone(SystemProperties.get(TIMEZONE_PROPERTY)); int gmtOffset = zone.getOffset(System.currentTimeMillis()); setKernelTimezone(mDescriptor, -(gmtOffset / 60000)); - scheduleDateChangedEvent(); + scheduleDateChangedEvent(); } } @@ -963,10 +1345,11 @@ class AlarmManagerService extends IAlarmManager.Stub { // the top of the next minute. final long tickEventDelay = nextTime - currentTime; - set(AlarmManager.ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, - mTimeTickSender); + final WorkSource workSource = null; // Let system take blame for time tick events. + set(ELAPSED_REALTIME, SystemClock.elapsedRealtime() + tickEventDelay, 0, + 0, mTimeTickSender, true, workSource); } - + public void scheduleDateChangedEvent() { Calendar calendar = Calendar.getInstance(); calendar.setTimeInMillis(System.currentTimeMillis()); @@ -975,8 +1358,9 @@ class AlarmManagerService extends IAlarmManager.Stub { calendar.set(Calendar.SECOND, 0); calendar.set(Calendar.MILLISECOND, 0); calendar.add(Calendar.DAY_OF_MONTH, 1); - - set(AlarmManager.RTC, calendar.getTimeInMillis(), mDateChangeSender); + + final WorkSource workSource = null; // Let system take blame for date change events. + set(RTC, calendar.getTimeInMillis(), 0, 0, mDateChangeSender, true, workSource); } } @@ -1092,7 +1476,8 @@ class AlarmManagerService extends IAlarmManager.Stub { } else { // the next of our alarms is now in flight. reattribute the wakelock. if (mInFlight.size() > 0) { - setWakelockWorkSource(mInFlight.get(0).mPendingIntent); + InFlight inFlight = mInFlight.get(0); + setWakelockWorkSource(inFlight.mPendingIntent, inFlight.mWorkSource); } else { // should never happen mLog.w("Alarm wakelock still held but sent queue empty"); diff --git a/services/java/com/android/server/AppOpsService.java b/services/java/com/android/server/AppOpsService.java index 20ad63683acd..a1a0d47cb019 100644 --- a/services/java/com/android/server/AppOpsService.java +++ b/services/java/com/android/server/AppOpsService.java @@ -41,6 +41,7 @@ import android.os.Process; import android.os.RemoteException; import android.os.ServiceManager; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.AtomicFile; import android.util.Log; import android.util.Pair; @@ -99,6 +100,8 @@ public class AppOpsService extends IAppOpsService.Stub { } public final static class Op { + public final int uid; + public final String packageName; public final int op; public int mode; public int duration; @@ -106,18 +109,20 @@ public class AppOpsService extends IAppOpsService.Stub { public long rejectTime; public int nesting; - public Op(int _op) { + public Op(int _uid, String _packageName, int _op) { + uid = _uid; + packageName = _packageName; op = _op; - mode = AppOpsManager.MODE_ALLOWED; + mode = AppOpsManager.opToDefaultMode(op); } } final SparseArray<ArrayList<Callback>> mOpModeWatchers = new SparseArray<ArrayList<Callback>>(); - final HashMap<String, ArrayList<Callback>> mPackageModeWatchers - = new HashMap<String, ArrayList<Callback>>(); - final HashMap<IBinder, Callback> mModeWatchers - = new HashMap<IBinder, Callback>(); + final ArrayMap<String, ArrayList<Callback>> mPackageModeWatchers + = new ArrayMap<String, ArrayList<Callback>>(); + final ArrayMap<IBinder, Callback> mModeWatchers + = new ArrayMap<IBinder, Callback>(); public final class Callback implements DeathRecipient { final IAppOpsCallback mCallback; @@ -140,12 +145,53 @@ public class AppOpsService extends IAppOpsService.Stub { } } + final ArrayMap<IBinder, ClientState> mClients = new ArrayMap<IBinder, ClientState>(); + + public final class ClientState extends Binder implements DeathRecipient { + final IBinder mAppToken; + final int mPid; + final ArrayList<Op> mStartedOps; + + public ClientState(IBinder appToken) { + mAppToken = appToken; + mPid = Binder.getCallingPid(); + if (appToken instanceof Binder) { + // For local clients, there is no reason to track them. + mStartedOps = null; + } else { + mStartedOps = new ArrayList<Op>(); + try { + mAppToken.linkToDeath(this, 0); + } catch (RemoteException e) { + } + } + } + + @Override + public String toString() { + return "ClientState{" + + "mAppToken=" + mAppToken + + ", " + (mStartedOps != null ? ("pid=" + mPid) : "local") + + '}'; + } + + @Override + public void binderDied() { + synchronized (AppOpsService.this) { + for (int i=mStartedOps.size()-1; i>=0; i--) { + finishOperationLocked(mStartedOps.get(i)); + } + mClients.remove(mAppToken); + } + } + } + public AppOpsService(File storagePath) { mFile = new AtomicFile(storagePath); mHandler = new Handler(); readState(); } - + public void publish(Context context) { mContext = context; ServiceManager.addService(Context.APP_OPS_SERVICE, asBinder()); @@ -333,7 +379,7 @@ public class AppOpsService extends IAppOpsService.Stub { } repCbs.addAll(cbs); } - if (mode == AppOpsManager.MODE_ALLOWED) { + if (mode == AppOpsManager.opToDefaultMode(op.op)) { // If going into the default mode, prune this op // if there is nothing else interesting in it. pruneOp(op, uid, packageName); @@ -380,23 +426,34 @@ public class AppOpsService extends IAppOpsService.Stub { HashMap<Callback, ArrayList<Pair<String, Integer>>> callbacks = null; synchronized (this) { boolean changed = false; - for (int i=0; i<mUidOps.size(); i++) { + for (int i=mUidOps.size()-1; i>=0; i--) { HashMap<String, Ops> packages = mUidOps.valueAt(i); - for (Map.Entry<String, Ops> ent : packages.entrySet()) { + Iterator<Map.Entry<String, Ops>> it = packages.entrySet().iterator(); + while (it.hasNext()) { + Map.Entry<String, Ops> ent = it.next(); String packageName = ent.getKey(); Ops pkgOps = ent.getValue(); - for (int j=0; j<pkgOps.size(); j++) { + for (int j=pkgOps.size()-1; j>=0; j--) { Op curOp = pkgOps.valueAt(j); - if (curOp.mode != AppOpsManager.MODE_ALLOWED) { - curOp.mode = AppOpsManager.MODE_ALLOWED; + if (AppOpsManager.opAllowsReset(curOp.op) + && curOp.mode != AppOpsManager.opToDefaultMode(curOp.op)) { + curOp.mode = AppOpsManager.opToDefaultMode(curOp.op); changed = true; callbacks = addCallbacks(callbacks, packageName, curOp.op, mOpModeWatchers.get(curOp.op)); callbacks = addCallbacks(callbacks, packageName, curOp.op, mPackageModeWatchers.get(packageName)); - pruneOp(curOp, mUidOps.keyAt(i), packageName); + if (curOp.time == 0 && curOp.rejectTime == 0) { + pkgOps.removeAt(j); + } } } + if (pkgOps.size() == 0) { + it.remove(); + } + } + if (packages.size() == 0) { + mUidOps.removeAt(i); } } if (changed) { @@ -452,21 +509,18 @@ public class AppOpsService extends IAppOpsService.Stub { Callback cb = mModeWatchers.remove(callback.asBinder()); if (cb != null) { cb.unlinkToDeath(); - for (int i=0; i<mOpModeWatchers.size(); i++) { + for (int i=mOpModeWatchers.size()-1; i>=0; i--) { ArrayList<Callback> cbs = mOpModeWatchers.valueAt(i); cbs.remove(cb); if (cbs.size() <= 0) { mOpModeWatchers.removeAt(i); } } - if (mPackageModeWatchers.size() > 0) { - Iterator<ArrayList<Callback>> it = mPackageModeWatchers.values().iterator(); - while (it.hasNext()) { - ArrayList<Callback> cbs = it.next(); - cbs.remove(cb); - if (cbs.size() <= 0) { - it.remove(); - } + for (int i=mPackageModeWatchers.size()-1; i>=0; i--) { + ArrayList<Callback> cbs = mPackageModeWatchers.valueAt(i); + cbs.remove(cb); + if (cbs.size() <= 0) { + mPackageModeWatchers.removeAt(i); } } } @@ -474,19 +528,42 @@ public class AppOpsService extends IAppOpsService.Stub { } @Override + public IBinder getToken(IBinder clientToken) { + synchronized (this) { + ClientState cs = mClients.get(clientToken); + if (cs == null) { + cs = new ClientState(clientToken); + mClients.put(clientToken, cs); + } + return cs; + } + } + + @Override public int checkOperation(int code, int uid, String packageName) { verifyIncomingUid(uid); verifyIncomingOp(code); synchronized (this) { Op op = getOpLocked(AppOpsManager.opToSwitch(code), uid, packageName, false); if (op == null) { - return AppOpsManager.MODE_ALLOWED; + return AppOpsManager.opToDefaultMode(code); } return op.mode; } } @Override + public int checkPackage(int uid, String packageName) { + synchronized (this) { + if (getOpsLocked(uid, packageName, true) != null) { + return AppOpsManager.MODE_ALLOWED; + } else { + return AppOpsManager.MODE_ERRORED; + } + } + } + + @Override public int noteOperation(int code, int uid, String packageName) { verifyIncomingUid(uid); verifyIncomingOp(code); @@ -495,7 +572,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (ops == null) { if (DEBUG) Log.d(TAG, "noteOperation: no op for code " + code + " uid " + uid + " package " + packageName); - return AppOpsManager.MODE_IGNORED; + return AppOpsManager.MODE_ERRORED; } Op op = getOpLocked(ops, code, true); if (op.duration == -1) { @@ -520,15 +597,16 @@ public class AppOpsService extends IAppOpsService.Stub { } @Override - public int startOperation(int code, int uid, String packageName) { + public int startOperation(IBinder token, int code, int uid, String packageName) { verifyIncomingUid(uid); verifyIncomingOp(code); + ClientState client = (ClientState)token; synchronized (this) { Ops ops = getOpsLocked(uid, packageName, true); if (ops == null) { if (DEBUG) Log.d(TAG, "startOperation: no op for code " + code + " uid " + uid + " package " + packageName); - return AppOpsManager.MODE_IGNORED; + return AppOpsManager.MODE_ERRORED; } Op op = getOpLocked(ops, code, true); final int switchCode = AppOpsManager.opToSwitch(code); @@ -547,32 +625,46 @@ public class AppOpsService extends IAppOpsService.Stub { op.duration = -1; } op.nesting++; + if (client.mStartedOps != null) { + client.mStartedOps.add(op); + } return AppOpsManager.MODE_ALLOWED; } } @Override - public void finishOperation(int code, int uid, String packageName) { + public void finishOperation(IBinder token, int code, int uid, String packageName) { verifyIncomingUid(uid); verifyIncomingOp(code); + ClientState client = (ClientState)token; synchronized (this) { Op op = getOpLocked(code, uid, packageName, true); if (op == null) { return; } - if (op.nesting <= 1) { - if (op.nesting == 1) { - op.duration = (int)(System.currentTimeMillis() - op.time); - op.time += op.duration; - } else { - Slog.w(TAG, "Finishing op nesting under-run: uid " + uid + " pkg " + packageName - + " code " + code + " time=" + op.time + " duration=" + op.duration - + " nesting=" + op.nesting); + if (client.mStartedOps != null) { + if (!client.mStartedOps.remove(op)) { + throw new IllegalStateException("Operation not started: uid" + op.uid + + " pkg=" + op.packageName + " op=" + op.op); } - op.nesting = 0; + } + finishOperationLocked(op); + } + } + + void finishOperationLocked(Op op) { + if (op.nesting <= 1) { + if (op.nesting == 1) { + op.duration = (int)(System.currentTimeMillis() - op.time); + op.time += op.duration; } else { - op.nesting--; + Slog.w(TAG, "Finishing op nesting under-run: uid " + op.uid + " pkg " + + op.packageName + " code " + op.op + " time=" + op.time + + " duration=" + op.duration + " nesting=" + op.nesting); } + op.nesting = 0; + } else { + op.nesting--; } } @@ -623,6 +715,9 @@ public class AppOpsService extends IAppOpsService.Stub { pkgUid = mContext.getPackageManager().getPackageUid(packageName, UserHandle.getUserId(uid)); } catch (NameNotFoundException e) { + if ("media".equals(packageName)) { + pkgUid = Process.MEDIA_UID; + } } if (pkgUid != uid) { // Oops! The package name is not valid for the uid they are calling @@ -670,7 +765,7 @@ public class AppOpsService extends IAppOpsService.Stub { if (!edit) { return null; } - op = new Op(code); + op = new Op(ops.uid, ops.packageName, code); ops.put(code, op); } if (edit) { @@ -780,7 +875,7 @@ public class AppOpsService extends IAppOpsService.Stub { String tagName = parser.getName(); if (tagName.equals("op")) { - Op op = new Op(Integer.parseInt(parser.getAttributeValue(null, "n"))); + Op op = new Op(uid, pkgName, Integer.parseInt(parser.getAttributeValue(null, "n"))); String mode = parser.getAttributeValue(null, "m"); if (mode != null) { op.mode = Integer.parseInt(mode); @@ -853,7 +948,7 @@ public class AppOpsService extends IAppOpsService.Stub { AppOpsManager.OpEntry op = ops.get(j); out.startTag(null, "op"); out.attribute(null, "n", Integer.toString(op.getOp())); - if (op.getMode() != AppOpsManager.MODE_ALLOWED) { + if (op.getMode() != AppOpsManager.opToDefaultMode(op.getOp())) { out.attribute(null, "m", Integer.toString(op.getMode())); } long time = op.getTime(); @@ -900,6 +995,62 @@ public class AppOpsService extends IAppOpsService.Stub { synchronized (this) { pw.println("Current AppOps Service state:"); final long now = System.currentTimeMillis(); + boolean needSep = false; + if (mOpModeWatchers.size() > 0) { + needSep = true; + pw.println(" Op mode watchers:"); + for (int i=0; i<mOpModeWatchers.size(); i++) { + pw.print(" Op "); pw.print(AppOpsManager.opToName(mOpModeWatchers.keyAt(i))); + pw.println(":"); + ArrayList<Callback> callbacks = mOpModeWatchers.valueAt(i); + for (int j=0; j<callbacks.size(); j++) { + pw.print(" #"); pw.print(j); pw.print(": "); + pw.println(callbacks.get(j)); + } + } + } + if (mPackageModeWatchers.size() > 0) { + needSep = true; + pw.println(" Package mode watchers:"); + for (int i=0; i<mPackageModeWatchers.size(); i++) { + pw.print(" Pkg "); pw.print(mPackageModeWatchers.keyAt(i)); + pw.println(":"); + ArrayList<Callback> callbacks = mPackageModeWatchers.valueAt(i); + for (int j=0; j<callbacks.size(); j++) { + pw.print(" #"); pw.print(j); pw.print(": "); + pw.println(callbacks.get(j)); + } + } + } + if (mModeWatchers.size() > 0) { + needSep = true; + pw.println(" All mode watchers:"); + for (int i=0; i<mModeWatchers.size(); i++) { + pw.print(" "); pw.print(mModeWatchers.keyAt(i)); + pw.print(" -> "); pw.println(mModeWatchers.valueAt(i)); + } + } + if (mClients.size() > 0) { + needSep = true; + pw.println(" Clients:"); + for (int i=0; i<mClients.size(); i++) { + pw.print(" "); pw.print(mClients.keyAt(i)); pw.println(":"); + ClientState cs = mClients.valueAt(i); + pw.print(" "); pw.println(cs); + if (cs.mStartedOps != null && cs.mStartedOps.size() > 0) { + pw.println(" Started ops:"); + for (int j=0; j<cs.mStartedOps.size(); j++) { + Op op = cs.mStartedOps.get(j); + pw.print(" "); pw.print("uid="); pw.print(op.uid); + pw.print(" pkg="); pw.print(op.packageName); + pw.print(" op="); pw.println(AppOpsManager.opToName(op.op)); + } + } + } + } + if (needSep) { + pw.println(); + } for (int i=0; i<mUidOps.size(); i++) { pw.print(" Uid "); UserHandle.formatUid(pw, mUidOps.keyAt(i)); pw.println(":"); HashMap<String, Ops> pkgOps = mUidOps.valueAt(i); diff --git a/services/java/com/android/server/AppWidgetService.java b/services/java/com/android/server/AppWidgetService.java index d5715a527f56..203cca692df0 100644 --- a/services/java/com/android/server/AppWidgetService.java +++ b/services/java/com/android/server/AppWidgetService.java @@ -27,7 +27,6 @@ import android.content.pm.PackageManager; import android.os.Binder; import android.os.Bundle; import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; import android.os.RemoteException; import android.os.UserHandle; @@ -37,6 +36,7 @@ import android.widget.RemoteViews; import com.android.internal.appwidget.IAppWidgetHost; import com.android.internal.appwidget.IAppWidgetService; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.IndentingPrintWriter; import java.io.FileDescriptor; @@ -63,16 +63,14 @@ class AppWidgetService extends IAppWidgetService.Stub AppWidgetService(Context context) { mContext = context; - HandlerThread handlerThread = new HandlerThread("AppWidgetService -- Save state"); - handlerThread.start(); - mSaveStateHandler = new Handler(handlerThread.getLooper()); + mSaveStateHandler = BackgroundThread.getHandler(); mAppWidgetServices = new SparseArray<AppWidgetServiceImpl>(5); AppWidgetServiceImpl primary = new AppWidgetServiceImpl(context, 0, mSaveStateHandler); mAppWidgetServices.append(0, primary); } - public void systemReady(boolean safeMode) { + public void systemRunning(boolean safeMode) { mSafeMode = safeMode; mAppWidgetServices.get(0).systemReady(safeMode); diff --git a/services/java/com/android/server/AppWidgetServiceImpl.java b/services/java/com/android/server/AppWidgetServiceImpl.java index fb2828b9cc39..69ae846597f7 100644 --- a/services/java/com/android/server/AppWidgetServiceImpl.java +++ b/services/java/com/android/server/AppWidgetServiceImpl.java @@ -86,9 +86,12 @@ import java.util.Set; class AppWidgetServiceImpl { + private static final String KEYGUARD_HOST_PACKAGE = "com.android.keyguard"; + private static final int KEYGUARD_HOST_ID = 0x4b455947; private static final String TAG = "AppWidgetServiceImpl"; private static final String SETTINGS_FILENAME = "appwidgets.xml"; private static final int MIN_UPDATE_PERIOD = 30 * 60 * 1000; // 30 minutes + private static final int CURRENT_VERSION = 1; // Bump if the stored widgets need to be upgraded. private static boolean DBG = false; @@ -1654,7 +1657,7 @@ class AppWidgetServiceImpl { out.setOutput(stream, "utf-8"); out.startDocument(null, true); out.startTag(null, "gs"); - + out.attribute(null, "version", String.valueOf(CURRENT_VERSION)); int providerIndex = 0; N = mInstalledProviders.size(); for (int i = 0; i < N; i++) { @@ -1723,6 +1726,7 @@ class AppWidgetServiceImpl { @SuppressWarnings("unused") void readStateFromFileLocked(FileInputStream stream) { boolean success = false; + int version = 0; try { XmlPullParser parser = Xml.newPullParser(); parser.setInput(stream, null); @@ -1734,7 +1738,14 @@ class AppWidgetServiceImpl { type = parser.next(); if (type == XmlPullParser.START_TAG) { String tag = parser.getName(); - if ("p".equals(tag)) { + if ("gs".equals(tag)) { + String attributeValue = parser.getAttributeValue(null, "version"); + try { + version = Integer.parseInt(attributeValue); + } catch (NumberFormatException e) { + version = 0; + } + } else if ("p".equals(tag)) { // TODO: do we need to check that this package has the same signature // as before? String pkg = parser.getAttributeValue(null, "pkg"); @@ -1873,6 +1884,8 @@ class AppWidgetServiceImpl { for (int i = mHosts.size() - 1; i >= 0; i--) { pruneHostLocked(mHosts.get(i)); } + // upgrade the database if needed + performUpgrade(version); } else { // failed reading, clean up Slog.w(TAG, "Failed to read state, clearing widgets and hosts."); @@ -1886,6 +1899,31 @@ class AppWidgetServiceImpl { } } + private void performUpgrade(int fromVersion) { + if (fromVersion < CURRENT_VERSION) { + Slog.v(TAG, "Upgrading widget database from " + fromVersion + " to " + CURRENT_VERSION + + " for user " + mUserId); + } + + int version = fromVersion; + + // Update 1: keyguard moved from package "android" to "com.android.keyguard" + if (version == 0) { + for (int i = 0; i < mHosts.size(); i++) { + Host host = mHosts.get(i); + if (host != null && "android".equals(host.packageName) + && host.hostId == KEYGUARD_HOST_ID) { + host.packageName = KEYGUARD_HOST_PACKAGE; + } + } + version = 1; + } + + if (version != CURRENT_VERSION) { + throw new IllegalStateException("Failed to upgrade widget database"); + } + } + static File getSettingsFile(int userId) { return new File(Environment.getUserSystemDirectory(userId), SETTINGS_FILENAME); } diff --git a/services/java/com/android/server/AssetAtlasService.java b/services/java/com/android/server/AssetAtlasService.java new file mode 100644 index 000000000000..26b4652f4c7d --- /dev/null +++ b/services/java/com/android/server/AssetAtlasService.java @@ -0,0 +1,735 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.content.Context; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.graphics.Atlas; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PixelFormat; +import android.graphics.PorterDuff; +import android.graphics.PorterDuffXfermode; +import android.graphics.drawable.Drawable; +import android.os.Environment; +import android.os.RemoteException; +import android.os.SystemProperties; +import android.util.Log; +import android.util.LongSparseArray; +import android.view.GraphicBuffer; +import android.view.IAssetAtlas; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStreamReader; +import java.io.OutputStreamWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * This service is responsible for packing preloaded bitmaps into a single + * atlas texture. The resulting texture can be shared across processes to + * reduce overall memory usage. + * + * @hide + */ +public class AssetAtlasService extends IAssetAtlas.Stub { + /** + * Name of the <code>AssetAtlasService</code>. + */ + public static final String ASSET_ATLAS_SERVICE = "assetatlas"; + + private static final String LOG_TAG = "Atlas"; + + // Turns debug logs on/off. Debug logs are kept to a minimum and should + // remain on to diagnose issues + private static final boolean DEBUG_ATLAS = true; + + // When set to true the content of the atlas will be saved to disk + // in /data/system/atlas.png. The shared GraphicBuffer may be empty + private static final boolean DEBUG_ATLAS_TEXTURE = false; + + // Minimum size in pixels to consider for the resulting texture + private static final int MIN_SIZE = 768; + // Maximum size in pixels to consider for the resulting texture + private static final int MAX_SIZE = 2048; + // Increment in number of pixels between size variants when looking + // for the best texture dimensions + private static final int STEP = 64; + + // This percentage of the total number of pixels represents the minimum + // number of pixels we want to be able to pack in the atlas + private static final float PACKING_THRESHOLD = 0.8f; + + // Defines the number of int fields used to represent a single entry + // in the atlas map. This number defines the size of the array returned + // by the getMap(). See the mAtlasMap field for more information + private static final int ATLAS_MAP_ENTRY_FIELD_COUNT = 4; + + // Specifies how our GraphicBuffer will be used. To get proper swizzling + // the buffer will be written to using OpenGL (from JNI) so we can leave + // the software flag set to "never" + private static final int GRAPHIC_BUFFER_USAGE = GraphicBuffer.USAGE_SW_READ_NEVER | + GraphicBuffer.USAGE_SW_WRITE_NEVER | GraphicBuffer.USAGE_HW_TEXTURE; + + // This boolean is set to true if an atlas was successfully + // computed and rendered + private final AtomicBoolean mAtlasReady = new AtomicBoolean(false); + + private final Context mContext; + + // Version name of the current build, used to identify changes to assets list + private final String mVersionName; + + // Holds the atlas' data. This buffer can be mapped to + // OpenGL using an EGLImage + private GraphicBuffer mBuffer; + + // Describes how bitmaps are placed in the atlas. Each bitmap is + // represented by several entries in the array: + // int0: SkBitmap*, the native bitmap object + // int1: x position + // int2: y position + // int3: rotated, 1 if the bitmap must be rotated, 0 otherwise + // NOTE: This will need to be handled differently to support 64 bit pointers + private int[] mAtlasMap; + + /** + * Creates a new service. Upon creating, the service will gather the list of + * assets to consider for packing into the atlas and spawn a new thread to + * start the packing work. + * + * @param context The context giving access to preloaded resources + */ + public AssetAtlasService(Context context) { + mContext = context; + mVersionName = queryVersionName(context); + + ArrayList<Bitmap> bitmaps = new ArrayList<Bitmap>(300); + int totalPixelCount = 0; + + // We only care about drawables that hold bitmaps + final Resources resources = context.getResources(); + final LongSparseArray<Drawable.ConstantState> drawables = resources.getPreloadedDrawables(); + + final int count = drawables.size(); + for (int i = 0; i < count; i++) { + final Bitmap bitmap = drawables.valueAt(i).getBitmap(); + if (bitmap != null && bitmap.getConfig() == Bitmap.Config.ARGB_8888) { + bitmaps.add(bitmap); + totalPixelCount += bitmap.getWidth() * bitmap.getHeight(); + } + } + + // Our algorithms perform better when the bitmaps are first sorted + // The comparator will sort the bitmap by width first, then by height + Collections.sort(bitmaps, new Comparator<Bitmap>() { + @Override + public int compare(Bitmap b1, Bitmap b2) { + if (b1.getWidth() == b2.getWidth()) { + return b2.getHeight() - b1.getHeight(); + } + return b2.getWidth() - b1.getWidth(); + } + }); + + // Kick off the packing work on a worker thread + new Thread(new Renderer(bitmaps, totalPixelCount)).start(); + } + + /** + * Queries the version name stored in framework's AndroidManifest. + * The version name can be used to identify possible changes to + * framework resources. + * + * @see #getBuildIdentifier(String) + */ + private static String queryVersionName(Context context) { + try { + String packageName = context.getPackageName(); + PackageInfo info = context.getPackageManager().getPackageInfo(packageName, 0); + return info.versionName; + } catch (PackageManager.NameNotFoundException e) { + Log.w(LOG_TAG, "Could not get package info", e); + } + return null; + } + + /** + * Callback invoked by the server thread to indicate we can now run + * 3rd party code. + */ + public void systemRunning() { + } + + /** + * The renderer does all the work: + */ + private class Renderer implements Runnable { + private final ArrayList<Bitmap> mBitmaps; + private final int mPixelCount; + + private int mNativeBitmap; + + // Used for debugging only + private Bitmap mAtlasBitmap; + + Renderer(ArrayList<Bitmap> bitmaps, int pixelCount) { + mBitmaps = bitmaps; + mPixelCount = pixelCount; + } + + /** + * 1. On first boot or after every update, brute-force through all the + * possible atlas configurations and look for the best one (maximimize + * number of packed assets and minimize texture size) + * a. If a best configuration was computed, write it out to disk for + * future use + * 2. Read best configuration from disk + * 3. Compute the packing using the best configuration + * 4. Allocate a GraphicBuffer + * 5. Render assets in the buffer + */ + @Override + public void run() { + Configuration config = chooseConfiguration(mBitmaps, mPixelCount, mVersionName); + if (DEBUG_ATLAS) Log.d(LOG_TAG, "Loaded configuration: " + config); + + if (config != null) { + mBuffer = GraphicBuffer.create(config.width, config.height, + PixelFormat.RGBA_8888, GRAPHIC_BUFFER_USAGE); + + if (mBuffer != null) { + Atlas atlas = new Atlas(config.type, config.width, config.height, config.flags); + if (renderAtlas(mBuffer, atlas, config.count)) { + mAtlasReady.set(true); + } + } + } + } + + /** + * Renders a list of bitmaps into the atlas. The position of each bitmap + * was decided by the packing algorithm and will be honored by this + * method. If need be this method will also rotate bitmaps. + * + * @param buffer The buffer to render the atlas entries into + * @param atlas The atlas to pack the bitmaps into + * @param packCount The number of bitmaps that will be packed in the atlas + * + * @return true if the atlas was rendered, false otherwise + */ + @SuppressWarnings("MismatchedReadAndWriteOfArray") + private boolean renderAtlas(GraphicBuffer buffer, Atlas atlas, int packCount) { + // Use a Source blend mode to improve performance, the target bitmap + // will be zero'd out so there's no need to waste time applying blending + final Paint paint = new Paint(); + paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC)); + + // We always render the atlas into a bitmap. This bitmap is then + // uploaded into the GraphicBuffer using OpenGL to swizzle the content + final Canvas canvas = acquireCanvas(buffer.getWidth(), buffer.getHeight()); + if (canvas == null) return false; + + final Atlas.Entry entry = new Atlas.Entry(); + + mAtlasMap = new int[packCount * ATLAS_MAP_ENTRY_FIELD_COUNT]; + int[] atlasMap = mAtlasMap; + int mapIndex = 0; + + boolean result = false; + try { + final long startRender = System.nanoTime(); + final int count = mBitmaps.size(); + + for (int i = 0; i < count; i++) { + final Bitmap bitmap = mBitmaps.get(i); + if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) { + // We have more bitmaps to pack than the current configuration + // says, we were most likely not able to detect a change in the + // list of preloaded drawables, abort and delete the configuration + if (mapIndex >= mAtlasMap.length) { + deleteDataFile(); + break; + } + + canvas.save(); + canvas.translate(entry.x, entry.y); + if (entry.rotated) { + canvas.translate(bitmap.getHeight(), 0.0f); + canvas.rotate(90.0f); + } + canvas.drawBitmap(bitmap, 0.0f, 0.0f, null); + canvas.restore(); + + atlasMap[mapIndex++] = bitmap.mNativeBitmap; + atlasMap[mapIndex++] = entry.x; + atlasMap[mapIndex++] = entry.y; + atlasMap[mapIndex++] = entry.rotated ? 1 : 0; + } + } + + final long endRender = System.nanoTime(); + if (mNativeBitmap != 0) { + result = nUploadAtlas(buffer, mNativeBitmap); + } + + final long endUpload = System.nanoTime(); + if (DEBUG_ATLAS) { + float renderDuration = (endRender - startRender) / 1000.0f / 1000.0f; + float uploadDuration = (endUpload - endRender) / 1000.0f / 1000.0f; + Log.d(LOG_TAG, String.format("Rendered atlas in %.2fms (%.2f+%.2fms)", + renderDuration + uploadDuration, renderDuration, uploadDuration)); + } + + } finally { + releaseCanvas(canvas); + } + + return result; + } + + /** + * Returns a Canvas for the specified buffer. If {@link #DEBUG_ATLAS_TEXTURE} + * is turned on, the returned Canvas will render into a local bitmap that + * will then be saved out to disk for debugging purposes. + * @param width + * @param height + */ + private Canvas acquireCanvas(int width, int height) { + if (DEBUG_ATLAS_TEXTURE) { + mAtlasBitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + return new Canvas(mAtlasBitmap); + } else { + Canvas canvas = new Canvas(); + mNativeBitmap = nAcquireAtlasCanvas(canvas, width, height); + return canvas; + } + } + + /** + * Releases the canvas used to render into the buffer. Calling this method + * will release any resource previously acquired. If {@link #DEBUG_ATLAS_TEXTURE} + * is turend on, calling this method will write the content of the atlas + * to disk in /data/system/atlas.png for debugging. + */ + private void releaseCanvas(Canvas canvas) { + if (DEBUG_ATLAS_TEXTURE) { + canvas.setBitmap(null); + + File systemDirectory = new File(Environment.getDataDirectory(), "system"); + File dataFile = new File(systemDirectory, "atlas.png"); + + try { + FileOutputStream out = new FileOutputStream(dataFile); + mAtlasBitmap.compress(Bitmap.CompressFormat.PNG, 100, out); + out.close(); + } catch (FileNotFoundException e) { + // Ignore + } catch (IOException e) { + // Ignore + } + + mAtlasBitmap.recycle(); + mAtlasBitmap = null; + } else { + nReleaseAtlasCanvas(canvas, mNativeBitmap); + } + } + } + + private static native int nAcquireAtlasCanvas(Canvas canvas, int width, int height); + private static native void nReleaseAtlasCanvas(Canvas canvas, int bitmap); + private static native boolean nUploadAtlas(GraphicBuffer buffer, int bitmap); + + @Override + public boolean isCompatible(int ppid) { + return ppid == android.os.Process.myPpid(); + } + + @Override + public GraphicBuffer getBuffer() throws RemoteException { + return mAtlasReady.get() ? mBuffer : null; + } + + @Override + public int[] getMap() throws RemoteException { + return mAtlasReady.get() ? mAtlasMap : null; + } + + /** + * Finds the best atlas configuration to pack the list of supplied bitmaps. + * This method takes advantage of multi-core systems by spawning a number + * of threads equal to the number of available cores. + */ + private static Configuration computeBestConfiguration( + ArrayList<Bitmap> bitmaps, int pixelCount) { + if (DEBUG_ATLAS) Log.d(LOG_TAG, "Computing best atlas configuration..."); + + long begin = System.nanoTime(); + List<WorkerResult> results = Collections.synchronizedList(new ArrayList<WorkerResult>()); + + // Don't bother with an extra thread if there's only one processor + int cpuCount = Runtime.getRuntime().availableProcessors(); + if (cpuCount == 1) { + new ComputeWorker(MIN_SIZE, MAX_SIZE, STEP, bitmaps, pixelCount, results, null).run(); + } else { + int start = MIN_SIZE; + int end = MAX_SIZE - (cpuCount - 1) * STEP; + int step = STEP * cpuCount; + + final CountDownLatch signal = new CountDownLatch(cpuCount); + + for (int i = 0; i < cpuCount; i++, start += STEP, end += STEP) { + ComputeWorker worker = new ComputeWorker(start, end, step, + bitmaps, pixelCount, results, signal); + new Thread(worker, "Atlas Worker #" + (i + 1)).start(); + } + + try { + signal.await(10, TimeUnit.SECONDS); + } catch (InterruptedException e) { + Log.w(LOG_TAG, "Could not complete configuration computation"); + return null; + } + } + + // Maximize the number of packed bitmaps, minimize the texture size + Collections.sort(results, new Comparator<WorkerResult>() { + @Override + public int compare(WorkerResult r1, WorkerResult r2) { + int delta = r2.count - r1.count; + if (delta != 0) return delta; + return r1.width * r1.height - r2.width * r2.height; + } + }); + + if (DEBUG_ATLAS) { + float delay = (System.nanoTime() - begin) / 1000.0f / 1000.0f / 1000.0f; + Log.d(LOG_TAG, String.format("Found best atlas configuration in %.2fs", delay)); + } + + WorkerResult result = results.get(0); + return new Configuration(result.type, result.width, result.height, result.count); + } + + /** + * Returns the path to the file containing the best computed + * atlas configuration. + */ + private static File getDataFile() { + File systemDirectory = new File(Environment.getDataDirectory(), "system"); + return new File(systemDirectory, "framework_atlas.config"); + } + + private static void deleteDataFile() { + Log.w(LOG_TAG, "Current configuration inconsistent with assets list"); + if (!getDataFile().delete()) { + Log.w(LOG_TAG, "Could not delete the current configuration"); + } + } + + private File getFrameworkResourcesFile() { + return new File(mContext.getApplicationInfo().sourceDir); + } + + /** + * Returns the best known atlas configuration. This method will either + * read the configuration from disk or start a brute-force search + * and save the result out to disk. + */ + private Configuration chooseConfiguration(ArrayList<Bitmap> bitmaps, int pixelCount, + String versionName) { + Configuration config = null; + + final File dataFile = getDataFile(); + if (dataFile.exists()) { + config = readConfiguration(dataFile, versionName); + } + + if (config == null) { + config = computeBestConfiguration(bitmaps, pixelCount); + if (config != null) writeConfiguration(config, dataFile, versionName); + } + + return config; + } + + /** + * Writes the specified atlas configuration to the specified file. + */ + private void writeConfiguration(Configuration config, File file, String versionName) { + BufferedWriter writer = null; + try { + writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(file))); + writer.write(getBuildIdentifier(versionName)); + writer.newLine(); + writer.write(config.type.toString()); + writer.newLine(); + writer.write(String.valueOf(config.width)); + writer.newLine(); + writer.write(String.valueOf(config.height)); + writer.newLine(); + writer.write(String.valueOf(config.count)); + writer.newLine(); + writer.write(String.valueOf(config.flags)); + writer.newLine(); + } catch (FileNotFoundException e) { + Log.w(LOG_TAG, "Could not write " + file, e); + } catch (IOException e) { + Log.w(LOG_TAG, "Could not write " + file, e); + } finally { + if (writer != null) { + try { + writer.close(); + } catch (IOException e) { + // Ignore + } + } + } + } + + /** + * Reads an atlas configuration from the specified file. This method + * returns null if an error occurs or if the configuration is invalid. + */ + private Configuration readConfiguration(File file, String versionName) { + BufferedReader reader = null; + Configuration config = null; + try { + reader = new BufferedReader(new InputStreamReader(new FileInputStream(file))); + + if (checkBuildIdentifier(reader, versionName)) { + Atlas.Type type = Atlas.Type.valueOf(reader.readLine()); + int width = readInt(reader, MIN_SIZE, MAX_SIZE); + int height = readInt(reader, MIN_SIZE, MAX_SIZE); + int count = readInt(reader, 0, Integer.MAX_VALUE); + int flags = readInt(reader, Integer.MIN_VALUE, Integer.MAX_VALUE); + + config = new Configuration(type, width, height, count, flags); + } + } catch (IllegalArgumentException e) { + Log.w(LOG_TAG, "Invalid parameter value in " + file, e); + } catch (FileNotFoundException e) { + Log.w(LOG_TAG, "Could not read " + file, e); + } catch (IOException e) { + Log.w(LOG_TAG, "Could not read " + file, e); + } finally { + if (reader != null) { + try { + reader.close(); + } catch (IOException e) { + // Ignore + } + } + } + return config; + } + + private static int readInt(BufferedReader reader, int min, int max) throws IOException { + return Math.max(min, Math.min(max, Integer.parseInt(reader.readLine()))); + } + + /** + * Compares the next line in the specified buffered reader to the current + * build identifier. Returns whether the two values are equal. + * + * @see #getBuildIdentifier(String) + */ + private boolean checkBuildIdentifier(BufferedReader reader, String versionName) + throws IOException { + String deviceBuildId = getBuildIdentifier(versionName); + String buildId = reader.readLine(); + return deviceBuildId.equals(buildId); + } + + /** + * Returns an identifier for the current build that can be used to detect + * likely changes to framework resources. The build identifier is made of + * several distinct values: + * + * build fingerprint/framework version name/file size of framework resources apk + * + * Only the build fingerprint should be necessary on user builds but + * the other values are useful to detect changes on eng builds during + * development. + * + * This identifier does not attempt to be exact: a new identifier does not + * necessarily mean the preloaded drawables have changed. It is important + * however that whenever the list of preloaded drawables changes, this + * identifier changes as well. + * + * @see #checkBuildIdentifier(java.io.BufferedReader, String) + */ + private String getBuildIdentifier(String versionName) { + return SystemProperties.get("ro.build.fingerprint", "") + '/' + versionName + '/' + + String.valueOf(getFrameworkResourcesFile().length()); + } + + /** + * Atlas configuration. Specifies the algorithm, dimensions and flags to use. + */ + private static class Configuration { + final Atlas.Type type; + final int width; + final int height; + final int count; + final int flags; + + Configuration(Atlas.Type type, int width, int height, int count) { + this(type, width, height, count, Atlas.FLAG_DEFAULTS); + } + + Configuration(Atlas.Type type, int width, int height, int count, int flags) { + this.type = type; + this.width = width; + this.height = height; + this.count = count; + this.flags = flags; + } + + @Override + public String toString() { + return type.toString() + " (" + width + "x" + height + ") flags=0x" + + Integer.toHexString(flags) + " count=" + count; + } + } + + /** + * Used during the brute-force search to gather information about each + * variant of the packing algorithm. + */ + private static class WorkerResult { + Atlas.Type type; + int width; + int height; + int count; + + WorkerResult(Atlas.Type type, int width, int height, int count) { + this.type = type; + this.width = width; + this.height = height; + this.count = count; + } + + @Override + public String toString() { + return String.format("%s %dx%d", type.toString(), width, height); + } + } + + /** + * A compute worker will try a finite number of variations of the packing + * algorithms and save the results in a supplied list. + */ + private static class ComputeWorker implements Runnable { + private final int mStart; + private final int mEnd; + private final int mStep; + private final List<Bitmap> mBitmaps; + private final List<WorkerResult> mResults; + private final CountDownLatch mSignal; + private final int mThreshold; + + /** + * Creates a new compute worker to brute-force through a range of + * packing algorithms variants. + * + * @param start The minimum texture width to try + * @param end The maximum texture width to try + * @param step The number of pixels to increment the texture width by at each step + * @param bitmaps The list of bitmaps to pack in the atlas + * @param pixelCount The total number of pixels occupied by the list of bitmaps + * @param results The list of results in which to save the brute-force search results + * @param signal Latch to decrement when this worker is done, may be null + */ + ComputeWorker(int start, int end, int step, List<Bitmap> bitmaps, int pixelCount, + List<WorkerResult> results, CountDownLatch signal) { + mStart = start; + mEnd = end; + mStep = step; + mBitmaps = bitmaps; + mResults = results; + mSignal = signal; + + // Minimum number of pixels we want to be able to pack + int threshold = (int) (pixelCount * PACKING_THRESHOLD); + // Make sure we can find at least one configuration + while (threshold > MAX_SIZE * MAX_SIZE) { + threshold >>= 1; + } + mThreshold = threshold; + } + + @Override + public void run() { + if (DEBUG_ATLAS) Log.d(LOG_TAG, "Running " + Thread.currentThread().getName()); + + Atlas.Entry entry = new Atlas.Entry(); + for (Atlas.Type type : Atlas.Type.values()) { + for (int width = mStart; width < mEnd; width += mStep) { + for (int height = MIN_SIZE; height < MAX_SIZE; height += STEP) { + // If the atlas is not big enough, skip it + if (width * height <= mThreshold) continue; + + final int count = packBitmaps(type, width, height, entry); + if (count > 0) { + mResults.add(new WorkerResult(type, width, height, count)); + // If we were able to pack everything let's stop here + // Increasing the height further won't make things better + if (count == mBitmaps.size()) { + break; + } + } + } + } + } + + if (mSignal != null) { + mSignal.countDown(); + } + } + + private int packBitmaps(Atlas.Type type, int width, int height, Atlas.Entry entry) { + int total = 0; + Atlas atlas = new Atlas(type, width, height); + + final int count = mBitmaps.size(); + for (int i = 0; i < count; i++) { + final Bitmap bitmap = mBitmaps.get(i); + if (atlas.pack(bitmap.getWidth(), bitmap.getHeight(), entry) != null) { + total++; + } + } + + return total; + } + } +} diff --git a/services/java/com/android/server/BackupManagerService.java b/services/java/com/android/server/BackupManagerService.java index a537e99dfa98..a04ee144da29 100644 --- a/services/java/com/android/server/BackupManagerService.java +++ b/services/java/com/android/server/BackupManagerService.java @@ -1305,9 +1305,6 @@ class BackupManagerService extends IBackupManager.Stub { mTransports.put(name, transport); } else { mTransports.remove(name); - if ((mCurrentTransport != null) && mCurrentTransport.equals(name)) { - mCurrentTransport = null; - } // Nothing further to do in the unregistration case return; } @@ -1995,6 +1992,15 @@ class BackupManagerService extends IBackupManager.Stub { return; } + if ((mCurrentPackage.applicationInfo.flags & ApplicationInfo.FLAG_STOPPED) != 0) { + // The app has been force-stopped or cleared or just installed, + // and not yet launched out of that state, so just as it won't + // receive broadcasts, we won't run it for backup. + addBackupTrace("skipping - stopped"); + executeNextState(BackupState.RUNNING_QUEUE); + return; + } + IBackupAgent agent = null; try { mWakelock.setWorkSource(new WorkSource(mCurrentPackage.applicationInfo.uid)); @@ -2877,7 +2883,7 @@ class BackupManagerService extends IBackupManager.Stub { // Save associated .obb content if it exists and we did save the apk // check for .obb and save those too final UserEnvironment userEnv = new UserEnvironment(UserHandle.USER_OWNER); - final File obbDir = userEnv.getExternalStorageAppObbDirectory(pkg.packageName); + final File obbDir = userEnv.buildExternalStorageAppObbDirs(pkg.packageName)[0]; if (obbDir != null) { if (MORE_DEBUG) Log.i(TAG, "obb dir: " + obbDir.getAbsolutePath()); File[] obbFiles = obbDir.listFiles(); @@ -5358,47 +5364,53 @@ class BackupManagerService extends IBackupManager.Stub { } // Enable/disable the backup service + @Override public void setBackupEnabled(boolean enable) { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.BACKUP, "setBackupEnabled"); Slog.i(TAG, "Backup enabled => " + enable); - boolean wasEnabled = mEnabled; - synchronized (this) { - Settings.Secure.putInt(mContext.getContentResolver(), - Settings.Secure.BACKUP_ENABLED, enable ? 1 : 0); - mEnabled = enable; - } + long oldId = Binder.clearCallingIdentity(); + try { + boolean wasEnabled = mEnabled; + synchronized (this) { + Settings.Secure.putInt(mContext.getContentResolver(), + Settings.Secure.BACKUP_ENABLED, enable ? 1 : 0); + mEnabled = enable; + } - synchronized (mQueueLock) { - if (enable && !wasEnabled && mProvisioned) { - // if we've just been enabled, start scheduling backup passes - startBackupAlarmsLocked(BACKUP_INTERVAL); - } else if (!enable) { - // No longer enabled, so stop running backups - if (DEBUG) Slog.i(TAG, "Opting out of backup"); - - mAlarmManager.cancel(mRunBackupIntent); - - // This also constitutes an opt-out, so we wipe any data for - // this device from the backend. We start that process with - // an alarm in order to guarantee wakelock states. - if (wasEnabled && mProvisioned) { - // NOTE: we currently flush every registered transport, not just - // the currently-active one. - HashSet<String> allTransports; - synchronized (mTransports) { - allTransports = new HashSet<String>(mTransports.keySet()); - } - // build the set of transports for which we are posting an init - for (String transport : allTransports) { - recordInitPendingLocked(true, transport); + synchronized (mQueueLock) { + if (enable && !wasEnabled && mProvisioned) { + // if we've just been enabled, start scheduling backup passes + startBackupAlarmsLocked(BACKUP_INTERVAL); + } else if (!enable) { + // No longer enabled, so stop running backups + if (DEBUG) Slog.i(TAG, "Opting out of backup"); + + mAlarmManager.cancel(mRunBackupIntent); + + // This also constitutes an opt-out, so we wipe any data for + // this device from the backend. We start that process with + // an alarm in order to guarantee wakelock states. + if (wasEnabled && mProvisioned) { + // NOTE: we currently flush every registered transport, not just + // the currently-active one. + HashSet<String> allTransports; + synchronized (mTransports) { + allTransports = new HashSet<String>(mTransports.keySet()); + } + // build the set of transports for which we are posting an init + for (String transport : allTransports) { + recordInitPendingLocked(true, transport); + } + mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), + mRunInitIntent); } - mAlarmManager.set(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(), - mRunInitIntent); } } + } finally { + Binder.restoreCallingIdentity(oldId); } } diff --git a/services/java/com/android/server/BatteryService.java b/services/java/com/android/server/BatteryService.java index 1f2947db7861..5f3f894ca68e 100644 --- a/services/java/com/android/server/BatteryService.java +++ b/services/java/com/android/server/BatteryService.java @@ -16,6 +16,7 @@ package com.android.server; +import android.os.BatteryStats; import com.android.internal.app.IBatteryStats; import com.android.server.am.BatteryStatsService; @@ -25,9 +26,12 @@ import android.content.Context; import android.content.Intent; import android.content.pm.PackageManager; import android.os.BatteryManager; +import android.os.BatteryProperties; import android.os.Binder; import android.os.FileUtils; import android.os.Handler; +import android.os.IBatteryPropertiesListener; +import android.os.IBatteryPropertiesRegistrar; import android.os.IBinder; import android.os.DropBoxManager; import android.os.RemoteException; @@ -88,8 +92,7 @@ public final class BatteryService extends Binder { private int mCriticalBatteryLevel; private static final int DUMP_MAX_LENGTH = 24 * 1024; - private static final String[] DUMPSYS_ARGS = new String[] { "--checkin", "-u" }; - private static final String BATTERY_STATS_SERVICE_NAME = "batteryinfo"; + private static final String[] DUMPSYS_ARGS = new String[] { "--checkin", "--unplugged" }; private static final String DUMPSYS_DATA_PATH = "/data/system/"; @@ -102,20 +105,8 @@ public final class BatteryService extends Binder { private final Object mLock = new Object(); - /* Begin native fields: All of these fields are set by native code. */ - private boolean mAcOnline; - private boolean mUsbOnline; - private boolean mWirelessOnline; - private int mBatteryStatus; - private int mBatteryHealth; - private boolean mBatteryPresent; - private int mBatteryLevel; - private int mBatteryVoltage; - private int mBatteryTemperature; - private String mBatteryTechnology; + private BatteryProperties mBatteryProps; private boolean mBatteryLevelCritical; - /* End native fields. */ - private int mLastBatteryStatus; private int mLastBatteryHealth; private boolean mLastBatteryPresent; @@ -143,7 +134,8 @@ public final class BatteryService extends Binder { private boolean mSentLowBatteryBroadcast = false; - private native void native_update(); + private BatteryListener mBatteryPropertiesListener; + private IBatteryPropertiesRegistrar mBatteryPropertiesRegistrar; public BatteryService(Context context, LightsService lights) { mContext = context; @@ -160,17 +152,21 @@ public final class BatteryService extends Binder { mShutdownBatteryTemperature = mContext.getResources().getInteger( com.android.internal.R.integer.config_shutdownBatteryTemperature); - mPowerSupplyObserver.startObserving("SUBSYSTEM=power_supply"); - // watch for invalid charger messages if the invalid_charger switch exists if (new File("/sys/devices/virtual/switch/invalid_charger/state").exists()) { mInvalidChargerObserver.startObserving( "DEVPATH=/devices/virtual/switch/invalid_charger"); } - // set initial status - synchronized (mLock) { - updateLocked(); + mBatteryPropertiesListener = new BatteryListener(); + + IBinder b = ServiceManager.getService("batterypropreg"); + mBatteryPropertiesRegistrar = IBatteryPropertiesRegistrar.Stub.asInterface(b); + + try { + mBatteryPropertiesRegistrar.registerListener(mBatteryPropertiesListener); + } catch (RemoteException e) { + // Should never happen. } } @@ -194,16 +190,16 @@ public final class BatteryService extends Binder { private boolean isPoweredLocked(int plugTypeSet) { // assume we are powered if battery state is unknown so // the "stay on while plugged in" option will work. - if (mBatteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) { + if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_UNKNOWN) { return true; } - if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mAcOnline) { + if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_AC) != 0 && mBatteryProps.chargerAcOnline) { return true; } - if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mUsbOnline) { + if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_USB) != 0 && mBatteryProps.chargerUsbOnline) { return true; } - if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mWirelessOnline) { + if ((plugTypeSet & BatteryManager.BATTERY_PLUGGED_WIRELESS) != 0 && mBatteryProps.chargerWirelessOnline) { return true; } return false; @@ -223,7 +219,7 @@ public final class BatteryService extends Binder { */ public int getBatteryLevel() { synchronized (mLock) { - return mBatteryLevel; + return mBatteryProps.batteryLevel; } } @@ -232,7 +228,7 @@ public final class BatteryService extends Binder { */ public boolean isBatteryLow() { synchronized (mLock) { - return mBatteryPresent && mBatteryLevel <= mLowBatteryWarningLevel; + return mBatteryProps.batteryPresent && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel; } } @@ -248,7 +244,7 @@ public final class BatteryService extends Binder { private void shutdownIfNoPowerLocked() { // shut down gracefully if our battery is critically low and we are not powered. // wait until the system has booted before attempting to display the shutdown dialog. - if (mBatteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) { + if (mBatteryProps.batteryLevel == 0 && !isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY)) { mHandler.post(new Runnable() { @Override public void run() { @@ -267,7 +263,7 @@ public final class BatteryService extends Binder { // shut down gracefully if temperature is too high (> 68.0C by default) // wait until the system has booted before attempting to display the // shutdown dialog. - if (mBatteryTemperature > mShutdownBatteryTemperature) { + if (mBatteryProps.batteryTemperature > mShutdownBatteryTemperature) { mHandler.post(new Runnable() { @Override public void run() { @@ -282,13 +278,13 @@ public final class BatteryService extends Binder { } } - private void updateLocked() { - if (!mUpdatesStopped) { - // Update the values of mAcOnline, et. all. - native_update(); - - // Process the new values. - processValuesLocked(); + private void update(BatteryProperties props) { + synchronized (mLock) { + if (!mUpdatesStopped) { + mBatteryProps = props; + // Process the new values. + processValuesLocked(); + } } } @@ -296,12 +292,12 @@ public final class BatteryService extends Binder { boolean logOutlier = false; long dischargeDuration = 0; - mBatteryLevelCritical = (mBatteryLevel <= mCriticalBatteryLevel); - if (mAcOnline) { + mBatteryLevelCritical = (mBatteryProps.batteryLevel <= mCriticalBatteryLevel); + if (mBatteryProps.chargerAcOnline) { mPlugType = BatteryManager.BATTERY_PLUGGED_AC; - } else if (mUsbOnline) { + } else if (mBatteryProps.chargerUsbOnline) { mPlugType = BatteryManager.BATTERY_PLUGGED_USB; - } else if (mWirelessOnline) { + } else if (mBatteryProps.chargerWirelessOnline) { mPlugType = BatteryManager.BATTERY_PLUGGED_WIRELESS; } else { mPlugType = BATTERY_PLUGGED_NONE; @@ -309,25 +305,27 @@ public final class BatteryService extends Binder { if (DEBUG) { Slog.d(TAG, "Processing new values: " - + "mAcOnline=" + mAcOnline - + ", mUsbOnline=" + mUsbOnline - + ", mWirelessOnline=" + mWirelessOnline - + ", mBatteryStatus=" + mBatteryStatus - + ", mBatteryHealth=" + mBatteryHealth - + ", mBatteryPresent=" + mBatteryPresent - + ", mBatteryLevel=" + mBatteryLevel - + ", mBatteryTechnology=" + mBatteryTechnology - + ", mBatteryVoltage=" + mBatteryVoltage - + ", mBatteryTemperature=" + mBatteryTemperature + + "chargerAcOnline=" + mBatteryProps.chargerAcOnline + + ", chargerUsbOnline=" + mBatteryProps.chargerUsbOnline + + ", chargerWirelessOnline=" + mBatteryProps.chargerWirelessOnline + + ", batteryStatus=" + mBatteryProps.batteryStatus + + ", batteryHealth=" + mBatteryProps.batteryHealth + + ", batteryPresent=" + mBatteryProps.batteryPresent + + ", batteryLevel=" + mBatteryProps.batteryLevel + + ", batteryTechnology=" + mBatteryProps.batteryTechnology + + ", batteryVoltage=" + mBatteryProps.batteryVoltage + + ", batteryCurrentNow=" + mBatteryProps.batteryCurrentNow + + ", batteryChargeCounter=" + mBatteryProps.batteryChargeCounter + + ", batteryTemperature=" + mBatteryProps.batteryTemperature + ", mBatteryLevelCritical=" + mBatteryLevelCritical + ", mPlugType=" + mPlugType); } // Let the battery stats keep track of the current level. try { - mBatteryStats.setBatteryState(mBatteryStatus, mBatteryHealth, - mPlugType, mBatteryLevel, mBatteryTemperature, - mBatteryVoltage); + mBatteryStats.setBatteryState(mBatteryProps.batteryStatus, mBatteryProps.batteryHealth, + mPlugType, mBatteryProps.batteryLevel, mBatteryProps.batteryTemperature, + mBatteryProps.batteryVoltage); } catch (RemoteException e) { // Should never happen. } @@ -335,13 +333,13 @@ public final class BatteryService extends Binder { shutdownIfNoPowerLocked(); shutdownIfOverTempLocked(); - if (mBatteryStatus != mLastBatteryStatus || - mBatteryHealth != mLastBatteryHealth || - mBatteryPresent != mLastBatteryPresent || - mBatteryLevel != mLastBatteryLevel || + if (mBatteryProps.batteryStatus != mLastBatteryStatus || + mBatteryProps.batteryHealth != mLastBatteryHealth || + mBatteryProps.batteryPresent != mLastBatteryPresent || + mBatteryProps.batteryLevel != mLastBatteryLevel || mPlugType != mLastPlugType || - mBatteryVoltage != mLastBatteryVoltage || - mBatteryTemperature != mLastBatteryTemperature || + mBatteryProps.batteryVoltage != mLastBatteryVoltage || + mBatteryProps.batteryTemperature != mLastBatteryTemperature || mInvalidCharger != mLastInvalidCharger) { if (mPlugType != mLastPlugType) { @@ -350,33 +348,33 @@ public final class BatteryService extends Binder { // There's no value in this data unless we've discharged at least once and the // battery level has changed; so don't log until it does. - if (mDischargeStartTime != 0 && mDischargeStartLevel != mBatteryLevel) { + if (mDischargeStartTime != 0 && mDischargeStartLevel != mBatteryProps.batteryLevel) { dischargeDuration = SystemClock.elapsedRealtime() - mDischargeStartTime; logOutlier = true; EventLog.writeEvent(EventLogTags.BATTERY_DISCHARGE, dischargeDuration, - mDischargeStartLevel, mBatteryLevel); + mDischargeStartLevel, mBatteryProps.batteryLevel); // make sure we see a discharge event before logging again mDischargeStartTime = 0; } } else if (mPlugType == BATTERY_PLUGGED_NONE) { // charging -> discharging or we just powered up mDischargeStartTime = SystemClock.elapsedRealtime(); - mDischargeStartLevel = mBatteryLevel; + mDischargeStartLevel = mBatteryProps.batteryLevel; } } - if (mBatteryStatus != mLastBatteryStatus || - mBatteryHealth != mLastBatteryHealth || - mBatteryPresent != mLastBatteryPresent || + if (mBatteryProps.batteryStatus != mLastBatteryStatus || + mBatteryProps.batteryHealth != mLastBatteryHealth || + mBatteryProps.batteryPresent != mLastBatteryPresent || mPlugType != mLastPlugType) { EventLog.writeEvent(EventLogTags.BATTERY_STATUS, - mBatteryStatus, mBatteryHealth, mBatteryPresent ? 1 : 0, - mPlugType, mBatteryTechnology); + mBatteryProps.batteryStatus, mBatteryProps.batteryHealth, mBatteryProps.batteryPresent ? 1 : 0, + mPlugType, mBatteryProps.batteryTechnology); } - if (mBatteryLevel != mLastBatteryLevel || - mBatteryVoltage != mLastBatteryVoltage || - mBatteryTemperature != mLastBatteryTemperature) { + if (mBatteryProps.batteryLevel != mLastBatteryLevel) { + // Don't do this just from voltage or temperature changes, that is + // too noisy. EventLog.writeEvent(EventLogTags.BATTERY_LEVEL, - mBatteryLevel, mBatteryVoltage, mBatteryTemperature); + mBatteryProps.batteryLevel, mBatteryProps.batteryVoltage, mBatteryProps.batteryTemperature); } if (mBatteryLevelCritical && !mLastBatteryLevelCritical && mPlugType == BATTERY_PLUGGED_NONE) { @@ -396,8 +394,8 @@ public final class BatteryService extends Binder { * (becomes <= mLowBatteryWarningLevel). */ final boolean sendBatteryLow = !plugged - && mBatteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN - && mBatteryLevel <= mLowBatteryWarningLevel + && mBatteryProps.batteryStatus != BatteryManager.BATTERY_STATUS_UNKNOWN + && mBatteryProps.batteryLevel <= mLowBatteryWarningLevel && (oldPlugged || mLastBatteryLevel > mLowBatteryWarningLevel); sendIntentLocked(); @@ -456,13 +454,13 @@ public final class BatteryService extends Binder { logOutlierLocked(dischargeDuration); } - mLastBatteryStatus = mBatteryStatus; - mLastBatteryHealth = mBatteryHealth; - mLastBatteryPresent = mBatteryPresent; - mLastBatteryLevel = mBatteryLevel; + mLastBatteryStatus = mBatteryProps.batteryStatus; + mLastBatteryHealth = mBatteryProps.batteryHealth; + mLastBatteryPresent = mBatteryProps.batteryPresent; + mLastBatteryLevel = mBatteryProps.batteryLevel; mLastPlugType = mPlugType; - mLastBatteryVoltage = mBatteryVoltage; - mLastBatteryTemperature = mBatteryTemperature; + mLastBatteryVoltage = mBatteryProps.batteryVoltage; + mLastBatteryTemperature = mBatteryProps.batteryTemperature; mLastBatteryLevelCritical = mBatteryLevelCritical; mLastInvalidCharger = mInvalidCharger; } @@ -474,29 +472,29 @@ public final class BatteryService extends Binder { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_REPLACE_PENDING); - int icon = getIconLocked(mBatteryLevel); + int icon = getIconLocked(mBatteryProps.batteryLevel); - intent.putExtra(BatteryManager.EXTRA_STATUS, mBatteryStatus); - intent.putExtra(BatteryManager.EXTRA_HEALTH, mBatteryHealth); - intent.putExtra(BatteryManager.EXTRA_PRESENT, mBatteryPresent); - intent.putExtra(BatteryManager.EXTRA_LEVEL, mBatteryLevel); + intent.putExtra(BatteryManager.EXTRA_STATUS, mBatteryProps.batteryStatus); + intent.putExtra(BatteryManager.EXTRA_HEALTH, mBatteryProps.batteryHealth); + intent.putExtra(BatteryManager.EXTRA_PRESENT, mBatteryProps.batteryPresent); + intent.putExtra(BatteryManager.EXTRA_LEVEL, mBatteryProps.batteryLevel); intent.putExtra(BatteryManager.EXTRA_SCALE, BATTERY_SCALE); intent.putExtra(BatteryManager.EXTRA_ICON_SMALL, icon); intent.putExtra(BatteryManager.EXTRA_PLUGGED, mPlugType); - intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryVoltage); - intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryTemperature); - intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryTechnology); + intent.putExtra(BatteryManager.EXTRA_VOLTAGE, mBatteryProps.batteryVoltage); + intent.putExtra(BatteryManager.EXTRA_TEMPERATURE, mBatteryProps.batteryTemperature); + intent.putExtra(BatteryManager.EXTRA_TECHNOLOGY, mBatteryProps.batteryTechnology); intent.putExtra(BatteryManager.EXTRA_INVALID_CHARGER, mInvalidCharger); if (DEBUG) { - Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. level:" + mBatteryLevel + - ", scale:" + BATTERY_SCALE + ", status:" + mBatteryStatus + - ", health:" + mBatteryHealth + ", present:" + mBatteryPresent + - ", voltage: " + mBatteryVoltage + - ", temperature: " + mBatteryTemperature + - ", technology: " + mBatteryTechnology + - ", AC powered:" + mAcOnline + ", USB powered:" + mUsbOnline + - ", Wireless powered:" + mWirelessOnline + + Slog.d(TAG, "Sending ACTION_BATTERY_CHANGED. level:" + mBatteryProps.batteryLevel + + ", scale:" + BATTERY_SCALE + ", status:" + mBatteryProps.batteryStatus + + ", health:" + mBatteryProps.batteryHealth + ", present:" + mBatteryProps.batteryPresent + + ", voltage: " + mBatteryProps.batteryVoltage + + ", temperature: " + mBatteryProps.batteryTemperature + + ", technology: " + mBatteryProps.batteryTechnology + + ", AC powered:" + mBatteryProps.chargerAcOnline + ", USB powered:" + mBatteryProps.chargerUsbOnline + + ", Wireless powered:" + mBatteryProps.chargerWirelessOnline + ", icon:" + icon + ", invalid charger:" + mInvalidCharger); } @@ -509,7 +507,7 @@ public final class BatteryService extends Binder { } private void logBatteryStatsLocked() { - IBinder batteryInfoService = ServiceManager.getService(BATTERY_STATS_SERVICE_NAME); + IBinder batteryInfoService = ServiceManager.getService(BatteryStats.SERVICE_NAME); if (batteryInfoService == null) return; DropBoxManager db = (DropBoxManager) mContext.getSystemService(Context.DROPBOX_SERVICE); @@ -519,7 +517,7 @@ public final class BatteryService extends Binder { FileOutputStream dumpStream = null; try { // dump the service to a file - dumpFile = new File(DUMPSYS_DATA_PATH + BATTERY_STATS_SERVICE_NAME + ".dump"); + dumpFile = new File(DUMPSYS_DATA_PATH + BatteryStats.SERVICE_NAME + ".dump"); dumpStream = new FileOutputStream(dumpFile); batteryInfoService.dump(dumpStream.getFD(), DUMPSYS_ARGS); FileUtils.sync(dumpStream); @@ -558,14 +556,14 @@ public final class BatteryService extends Binder { long durationThreshold = Long.parseLong(durationThresholdString); int dischargeThreshold = Integer.parseInt(dischargeThresholdString); if (duration <= durationThreshold && - mDischargeStartLevel - mBatteryLevel >= dischargeThreshold) { + mDischargeStartLevel - mBatteryProps.batteryLevel >= dischargeThreshold) { // If the discharge cycle is bad enough we want to know about it. logBatteryStatsLocked(); } if (DEBUG) Slog.v(TAG, "duration threshold: " + durationThreshold + " discharge threshold: " + dischargeThreshold); if (DEBUG) Slog.v(TAG, "duration: " + duration + " discharge: " + - (mDischargeStartLevel - mBatteryLevel)); + (mDischargeStartLevel - mBatteryProps.batteryLevel)); } catch (NumberFormatException e) { Slog.e(TAG, "Invalid DischargeThresholds GService string: " + durationThresholdString + " or " + dischargeThresholdString); @@ -575,14 +573,14 @@ public final class BatteryService extends Binder { } private int getIconLocked(int level) { - if (mBatteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) { + if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_CHARGING) { return com.android.internal.R.drawable.stat_sys_battery_charge; - } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) { + } else if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_DISCHARGING) { return com.android.internal.R.drawable.stat_sys_battery; - } else if (mBatteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING - || mBatteryStatus == BatteryManager.BATTERY_STATUS_FULL) { + } else if (mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_NOT_CHARGING + || mBatteryProps.batteryStatus == BatteryManager.BATTERY_STATUS_FULL) { if (isPoweredLocked(BatteryManager.BATTERY_PLUGGED_ANY) - && mBatteryLevel >= 100) { + && mBatteryProps.batteryLevel >= 100) { return com.android.internal.R.drawable.stat_sys_battery_charge; } else { return com.android.internal.R.drawable.stat_sys_battery; @@ -609,32 +607,41 @@ public final class BatteryService extends Binder { if (mUpdatesStopped) { pw.println(" (UPDATES STOPPED -- use 'reset' to restart)"); } - pw.println(" AC powered: " + mAcOnline); - pw.println(" USB powered: " + mUsbOnline); - pw.println(" Wireless powered: " + mWirelessOnline); - pw.println(" status: " + mBatteryStatus); - pw.println(" health: " + mBatteryHealth); - pw.println(" present: " + mBatteryPresent); - pw.println(" level: " + mBatteryLevel); + pw.println(" AC powered: " + mBatteryProps.chargerAcOnline); + pw.println(" USB powered: " + mBatteryProps.chargerUsbOnline); + pw.println(" Wireless powered: " + mBatteryProps.chargerWirelessOnline); + pw.println(" status: " + mBatteryProps.batteryStatus); + pw.println(" health: " + mBatteryProps.batteryHealth); + pw.println(" present: " + mBatteryProps.batteryPresent); + pw.println(" level: " + mBatteryProps.batteryLevel); pw.println(" scale: " + BATTERY_SCALE); - pw.println(" voltage:" + mBatteryVoltage); - pw.println(" temperature: " + mBatteryTemperature); - pw.println(" technology: " + mBatteryTechnology); + pw.println(" voltage: " + mBatteryProps.batteryVoltage); + + if (mBatteryProps.batteryCurrentNow != Integer.MIN_VALUE) { + pw.println(" current now: " + mBatteryProps.batteryCurrentNow); + } + + if (mBatteryProps.batteryChargeCounter != Integer.MIN_VALUE) { + pw.println(" charge counter: " + mBatteryProps.batteryChargeCounter); + } + + pw.println(" temperature: " + mBatteryProps.batteryTemperature); + pw.println(" technology: " + mBatteryProps.batteryTechnology); } else if (args.length == 3 && "set".equals(args[0])) { String key = args[1]; String value = args[2]; try { boolean update = true; if ("ac".equals(key)) { - mAcOnline = Integer.parseInt(value) != 0; + mBatteryProps.chargerAcOnline = Integer.parseInt(value) != 0; } else if ("usb".equals(key)) { - mUsbOnline = Integer.parseInt(value) != 0; + mBatteryProps.chargerUsbOnline = Integer.parseInt(value) != 0; } else if ("wireless".equals(key)) { - mWirelessOnline = Integer.parseInt(value) != 0; + mBatteryProps.chargerWirelessOnline = Integer.parseInt(value) != 0; } else if ("status".equals(key)) { - mBatteryStatus = Integer.parseInt(value); + mBatteryProps.batteryStatus = Integer.parseInt(value); } else if ("level".equals(key)) { - mBatteryLevel = Integer.parseInt(value); + mBatteryProps.batteryLevel = Integer.parseInt(value); } else if ("invalid".equals(key)) { mInvalidCharger = Integer.parseInt(value); } else { @@ -642,15 +649,24 @@ public final class BatteryService extends Binder { update = false; } if (update) { - mUpdatesStopped = true; - processValuesLocked(); + long ident = Binder.clearCallingIdentity(); + try { + mUpdatesStopped = true; + processValuesLocked(); + } finally { + Binder.restoreCallingIdentity(ident); + } } } catch (NumberFormatException ex) { pw.println("Bad value: " + value); } } else if (args.length == 1 && "reset".equals(args[0])) { - mUpdatesStopped = false; - updateLocked(); + long ident = Binder.clearCallingIdentity(); + try { + mUpdatesStopped = false; + } finally { + Binder.restoreCallingIdentity(ident); + } } else { pw.println("Dump current battery state, or:"); pw.println(" set ac|usb|wireless|status|level|invalid <value>"); @@ -659,15 +675,6 @@ public final class BatteryService extends Binder { } } - private final UEventObserver mPowerSupplyObserver = new UEventObserver() { - @Override - public void onUEvent(UEventObserver.UEvent event) { - synchronized (mLock) { - updateLocked(); - } - } - }; - private final UEventObserver mInvalidChargerObserver = new UEventObserver() { @Override public void onUEvent(UEventObserver.UEvent event) { @@ -675,7 +682,6 @@ public final class BatteryService extends Binder { synchronized (mLock) { if (mInvalidCharger != invalidCharger) { mInvalidCharger = invalidCharger; - updateLocked(); } } } @@ -709,8 +715,8 @@ public final class BatteryService extends Binder { * Synchronize on BatteryService. */ public void updateLightsLocked() { - final int level = mBatteryLevel; - final int status = mBatteryStatus; + final int level = mBatteryProps.batteryLevel; + final int status = mBatteryProps.batteryStatus; if (level < mLowBatteryWarningLevel) { if (status == BatteryManager.BATTERY_STATUS_CHARGING) { // Solid red when battery is charging @@ -735,4 +741,10 @@ public final class BatteryService extends Binder { } } } + + private final class BatteryListener extends IBatteryPropertiesListener.Stub { + public void batteryPropertiesChanged(BatteryProperties props) { + BatteryService.this.update(props); + } + } } diff --git a/services/java/com/android/server/BluetoothManagerService.java b/services/java/com/android/server/BluetoothManagerService.java index bea2ccacbdc5..546324a73e05 100644 --- a/services/java/com/android/server/BluetoothManagerService.java +++ b/services/java/com/android/server/BluetoothManagerService.java @@ -34,7 +34,6 @@ import android.content.ServiceConnection; import android.content.pm.PackageManager; import android.os.Binder; import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; @@ -120,7 +119,6 @@ class BluetoothManagerService extends IBluetoothManager.Stub { // used inside handler thread private boolean mEnable; private int mState; - private HandlerThread mThread; private final BluetoothHandler mHandler; private int mErrorRecoveryRetryCounter; @@ -194,9 +192,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { }; BluetoothManagerService(Context context) { - mThread = new HandlerThread("BluetoothManager"); - mThread.start(); - mHandler = new BluetoothHandler(mThread.getLooper()); + mHandler = new BluetoothHandler(IoThread.get().getLooper()); mContext = context; mBluetooth = null; @@ -647,10 +643,9 @@ class BluetoothManagerService extends IBluetoothManager.Stub { Message timeoutMsg = mHandler.obtainMessage(MESSAGE_TIMEOUT_BIND); mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS); Intent i = new Intent(IBluetooth.class.getName()); - if (!mContext.bindServiceAsUser(i, mConnection, - Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) { + if (!doBind(i, mConnection, + Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) { mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); - Log.e(TAG, "fail to bind to: " + IBluetooth.class.getName()); } else { mBinding = true; } @@ -771,13 +766,17 @@ class BluetoothManagerService extends IBluetoothManager.Stub { case MESSAGE_REGISTER_STATE_CHANGE_CALLBACK: { IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj; - mStateChangeCallbacks.register(callback); + if (callback != null) { + mStateChangeCallbacks.register(callback); + } break; } case MESSAGE_UNREGISTER_STATE_CHANGE_CALLBACK: { IBluetoothStateChangeCallback callback = (IBluetoothStateChangeCallback) msg.obj; - mStateChangeCallbacks.unregister(callback); + if (callback != null) { + mStateChangeCallbacks.unregister(callback); + } break; } case MESSAGE_BLUETOOTH_SERVICE_CONNECTED: @@ -792,11 +791,21 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } // else must be SERVICE_IBLUETOOTH //Remove timeout - mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); + mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); mBinding = false; mBluetooth = IBluetooth.Stub.asInterface(service); + try { + boolean enableHciSnoopLog = (Settings.Secure.getInt(mContentResolver, + Settings.Secure.BLUETOOTH_HCI_LOG, 0) == 1); + if (!mBluetooth.configHciSnoopLog(enableHciSnoopLog)) { + Log.e(TAG,"IBluetooth.configHciSnoopLog return false"); + } + } catch (RemoteException e) { + Log.e(TAG,"Unable to call configHciSnoopLog", e); + } + if (mConnection.isGetNameAddressOnly()) { //Request GET NAME AND ADDRESS Message getMsg = mHandler.obtainMessage(MESSAGE_GET_NAME_AND_ADDRESS); @@ -1022,10 +1031,8 @@ class BluetoothManagerService extends IBluetoothManager.Stub { mHandler.sendMessageDelayed(timeoutMsg,TIMEOUT_BIND_MS); mConnection.setGetNameAddressOnly(false); Intent i = new Intent(IBluetooth.class.getName()); - if (!mContext.bindServiceAsUser(i, mConnection,Context.BIND_AUTO_CREATE, - UserHandle.CURRENT)) { + if (!doBind(i, mConnection,Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) { mHandler.removeMessages(MESSAGE_TIMEOUT_BIND); - Log.e(TAG, "Fail to bind to: " + IBluetooth.class.getName()); } else { mBinding = true; } @@ -1064,6 +1071,16 @@ class BluetoothManagerService extends IBluetoothManager.Stub { } } + boolean doBind(Intent intent, ServiceConnection conn, int flags, UserHandle user) { + ComponentName comp = intent.resolveSystemService(mContext.getPackageManager(), 0); + intent.setComponent(comp); + if (comp == null || !mContext.bindServiceAsUser(intent, conn, flags, user)) { + Log.e(TAG, "Fail to bind to: " + intent); + return false; + } + return true; + } + private void handleDisable() { synchronized(mConnection) { // don't need to disable if GetNameAddressOnly is set, @@ -1116,10 +1133,7 @@ class BluetoothManagerService extends IBluetoothManager.Stub { if (mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_BLUETOOTH_LE)) { Intent i = new Intent(IBluetoothGatt.class.getName()); - if (!mContext.bindServiceAsUser(i, mConnection, Context.BIND_AUTO_CREATE, - UserHandle.CURRENT)) { - Log.e(TAG, "Fail to bind to: " + IBluetoothGatt.class.getName()); - } + doBind(i, mConnection, Context.BIND_AUTO_CREATE, UserHandle.CURRENT); } } else { //If Bluetooth is off, send service down event to proxy objects, and unbind diff --git a/services/java/com/android/server/BootReceiver.java b/services/java/com/android/server/BootReceiver.java index 3dade3733bb6..da1b2548238f 100644 --- a/services/java/com/android/server/BootReceiver.java +++ b/services/java/com/android/server/BootReceiver.java @@ -127,6 +127,7 @@ public class BootReceiver extends BroadcastReceiver { addFileToDropBox(db, prefs, headers, "/data/dontpanic/apanic_threads", -LOG_SIZE, "APANIC_THREADS"); addAuditErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_AUDIT"); + addFsckErrorsToDropBox(db, prefs, headers, -LOG_SIZE, "SYSTEM_FSCK"); } else { if (db != null) db.addText("SYSTEM_RESTART", headers); } @@ -203,4 +204,31 @@ public class BootReceiver extends BroadcastReceiver { Slog.i(TAG, "Copied " + sb.toString().length() + " worth of audits to DropBox"); db.addText(tag, headers + sb.toString()); } + + private static void addFsckErrorsToDropBox(DropBoxManager db, SharedPreferences prefs, + String headers, int maxSize, String tag) throws IOException { + boolean upload_needed = false; + if (db == null || !db.isTagEnabled(tag)) return; // Logging disabled + Slog.i(TAG, "Checking for fsck errors"); + + File file = new File("/dev/fscklogs/log"); + long fileTime = file.lastModified(); + if (fileTime <= 0) return; // File does not exist + + String log = FileUtils.readTextFile(file, maxSize, "[[TRUNCATED]]\n"); + StringBuilder sb = new StringBuilder(); + for (String line : log.split("\n")) { + if (line.contains("FILE SYSTEM WAS MODIFIED")) { + upload_needed = true; + break; + } + } + + if (upload_needed) { + addFileToDropBox(db, prefs, headers, "/dev/fscklogs/log", maxSize, tag); + } + + // Remove the file so we don't re-upload if the runtime restarts. + file.delete(); + } } diff --git a/services/java/com/android/server/ClipboardService.java b/services/java/com/android/server/ClipboardService.java index 0bf03b5ff453..069ae23f5596 100644 --- a/services/java/com/android/server/ClipboardService.java +++ b/services/java/com/android/server/ClipboardService.java @@ -122,7 +122,9 @@ public class ClipboardService extends IClipboard.Stub { try { return super.onTransact(code, data, reply, flags); } catch (RuntimeException e) { - Slog.w("clipboard", "Exception: ", e); + if (!(e instanceof SecurityException)) { + Slog.wtf("clipboard", "Exception: ", e); + } throw e; } diff --git a/services/java/com/android/server/CommonTimeManagementService.java b/services/java/com/android/server/CommonTimeManagementService.java index c316733fe058..710fb9d34596 100644 --- a/services/java/com/android/server/CommonTimeManagementService.java +++ b/services/java/com/android/server/CommonTimeManagementService.java @@ -40,6 +40,8 @@ import android.os.ServiceManager; import android.os.SystemProperties; import android.util.Log; +import com.android.server.net.BaseNetworkObserver; + /** * @hide * <p>CommonTimeManagementService manages the configuration of the native Common Time service, @@ -104,9 +106,7 @@ class CommonTimeManagementService extends Binder { /* * Callback handler implementations. */ - private INetworkManagementEventObserver mIfaceObserver = - new INetworkManagementEventObserver.Stub() { - + private INetworkManagementEventObserver mIfaceObserver = new BaseNetworkObserver() { public void interfaceStatusChanged(String iface, boolean up) { reevaluateServiceState(); } @@ -119,9 +119,6 @@ class CommonTimeManagementService extends Binder { public void interfaceRemoved(String iface) { reevaluateServiceState(); } - public void limitReached(String limitName, String iface) { } - - public void interfaceClassDataActivityChanged(String label, boolean active) {} }; private BroadcastReceiver mConnectivityMangerObserver = new BroadcastReceiver() { @@ -153,7 +150,7 @@ class CommonTimeManagementService extends Binder { mContext = context; } - void systemReady() { + void systemRunning() { if (ServiceManager.checkService(CommonTimeConfig.SERVICE_NAME) == null) { Log.i(TAG, "No common time service detected on this platform. " + "Common time services will be unavailable."); diff --git a/services/java/com/android/server/ConnectivityService.java b/services/java/com/android/server/ConnectivityService.java index 8c388edb149e..a9b4f19f7a33 100644 --- a/services/java/com/android/server/ConnectivityService.java +++ b/services/java/com/android/server/ConnectivityService.java @@ -31,10 +31,12 @@ import static android.net.ConnectivityManager.isNetworkTypeValid; import static android.net.NetworkPolicyManager.RULE_ALLOW_ALL; import static android.net.NetworkPolicyManager.RULE_REJECT_METERED; +import android.app.AlarmManager; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.bluetooth.BluetoothTetheringDataTracker; +import android.content.ActivityNotFoundException; import android.content.BroadcastReceiver; import android.content.ContentResolver; import android.content.Context; @@ -56,13 +58,12 @@ import android.net.INetworkPolicyManager; import android.net.INetworkStatsService; import android.net.LinkAddress; import android.net.LinkProperties; -import android.net.Uri; import android.net.LinkProperties.CompareResult; +import android.net.LinkQualityInfo; import android.net.MobileDataStateTracker; import android.net.NetworkConfig; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; -import android.net.NetworkInfo.State; import android.net.NetworkQuotaInfo; import android.net.NetworkState; import android.net.NetworkStateTracker; @@ -70,6 +71,8 @@ import android.net.NetworkUtils; import android.net.Proxy; import android.net.ProxyProperties; import android.net.RouteInfo; +import android.net.SamplingDataTracker; +import android.net.Uri; import android.net.wifi.WifiStateTracker; import android.net.wimax.WimaxManagerConstants; import android.os.AsyncTask; @@ -86,7 +89,6 @@ import android.os.ParcelFileDescriptor; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; -import android.os.ResultReceiver; import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; @@ -97,10 +99,12 @@ import android.security.KeyStore; import android.telephony.TelephonyManager; import android.text.TextUtils; import android.util.Slog; +import android.util.SparseArray; import android.util.SparseIntArray; import android.util.Xml; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; import com.android.internal.net.VpnProfile; @@ -110,7 +114,9 @@ import com.android.internal.telephony.PhoneConstants; import com.android.internal.util.IndentingPrintWriter; import com.android.internal.util.XmlUtils; import com.android.server.am.BatteryStatsService; +import com.android.server.connectivity.DataConnectionStats; import com.android.server.connectivity.Nat464Xlat; +import com.android.server.connectivity.PacManager; import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; import com.android.server.net.BaseNetworkObserver; @@ -140,9 +146,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.GregorianCalendar; +import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.Random; +import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicInteger; /** @@ -170,6 +179,23 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final String FAIL_FAST_TIME_MS = "persist.radio.fail_fast_time_ms"; + private static final String ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED = + "android.net.ConnectivityService.action.PKT_CNT_SAMPLE_INTERVAL_ELAPSED"; + + private static final int SAMPLE_INTERVAL_ELAPSED_REQUEST_CODE = 0; + + private PendingIntent mSampleIntervalElapsedIntent; + + // Set network sampling interval at 12 minutes, this way, even if the timers get + // aggregated, it will fire at around 15 minutes, which should allow us to + // aggregate this timer with other timers (specially the socket keep alive timers) + private static final int DEFAULT_SAMPLING_INTERVAL_IN_SECONDS = (VDBG ? 30 : 12 * 60); + + // start network sampling a minute after booting ... + private static final int DEFAULT_START_SAMPLING_INTERVAL_IN_SECONDS = (VDBG ? 30 : 60); + + AlarmManager mAlarmManager; + // used in recursive route setting to add gateways for the host for which // a host route was requested. private static final int MAX_HOSTROUTE_CYCLE_COUNT = 10; @@ -178,7 +204,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { private KeyStore mKeyStore; - private Vpn mVpn; + @GuardedBy("mVpns") + private final SparseArray<Vpn> mVpns = new SparseArray<Vpn>(); private VpnCallback mVpnCallback = new VpnCallback(); private boolean mLockdownEnabled; @@ -230,7 +257,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { private Object mDnsLock = new Object(); private int mNumDnsEntries; - private boolean mDnsOverridden = false; private boolean mTestMode; private static ConnectivityService sServiceInstance; @@ -247,6 +273,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final boolean TO_DEFAULT_TABLE = true; private static final boolean TO_SECONDARY_TABLE = false; + private static final boolean EXEMPT = true; + private static final boolean UNEXEMPT = false; + /** * used internally as a delayed event to make us switch back to the * default network @@ -302,28 +331,32 @@ public class ConnectivityService extends IConnectivityManager.Stub { private static final int EVENT_SET_DEPENDENCY_MET = 10; /** - * used internally to restore DNS properties back to the - * default network - */ - private static final int EVENT_RESTORE_DNS = 11; - - /** * used internally to send a sticky broadcast delayed. */ - private static final int EVENT_SEND_STICKY_BROADCAST_INTENT = 12; + private static final int EVENT_SEND_STICKY_BROADCAST_INTENT = 11; /** * Used internally to * {@link NetworkStateTracker#setPolicyDataEnable(boolean)}. */ - private static final int EVENT_SET_POLICY_DATA_ENABLE = 13; + private static final int EVENT_SET_POLICY_DATA_ENABLE = 12; - private static final int EVENT_VPN_STATE_CHANGED = 14; + private static final int EVENT_VPN_STATE_CHANGED = 13; /** * Used internally to disable fail fast of mobile data */ - private static final int EVENT_ENABLE_FAIL_FAST_MOBILE_DATA = 15; + private static final int EVENT_ENABLE_FAIL_FAST_MOBILE_DATA = 14; + + /** + * user internally to indicate that data sampling interval is up + */ + private static final int EVENT_SAMPLE_INTERVAL_ELAPSED = 15; + + /** + * PAC manager has received new port. + */ + private static final int EVENT_PROXY_HAS_CHANGED = 16; /** Handler used for internal events. */ private InternalHandler mHandler; @@ -344,10 +377,19 @@ public class ConnectivityService extends IConnectivityManager.Stub { private InetAddress mDefaultDns; + // Lock for protecting access to mAddedRoutes and mExemptAddresses + private final Object mRoutesLock = new Object(); + // this collection is used to refcount the added routes - if there are none left // it's time to remove the route from the route table + @GuardedBy("mRoutesLock") private Collection<RouteInfo> mAddedRoutes = new ArrayList<RouteInfo>(); + // this collection corresponds to the entries of mAddedRoutes that have routing exemptions + // used to handle cleanup of exempt rules + @GuardedBy("mRoutesLock") + private Collection<LinkAddress> mExemptAddresses = new ArrayList<LinkAddress>(); + // used in DBG mode to track inet condition reports private static final int INET_CONDITION_LOG_MAX_SIZE = 15; private ArrayList mInetLog; @@ -360,6 +402,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { // track the global proxy. private ProxyProperties mGlobalProxy = null; + private PacManager mPacManager = null; + private SettingsObserver mSettingsObserver; NetworkConfig[] mNetConfigs; @@ -379,13 +423,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { // the set of network types that can only be enabled by system/sig apps List mProtectedNetworks; + private DataConnectionStats mDataConnectionStats; + private AtomicInteger mEnableFailFastMobileDataTag = new AtomicInteger(0); TelephonyManager mTelephonyManager; - // We only want one checkMobileProvisioning after booting. - volatile boolean mFirstProvisioningCheckStarted = false; - public ConnectivityService(Context context, INetworkManagementService netd, INetworkStatsService statsService, INetworkPolicyManager policyManager) { // Currently, omitting a NetworkFactory will create one internally @@ -461,6 +504,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { com.android.internal.R.array.radioAttributes); for (String raString : raStrings) { RadioAttributes r = new RadioAttributes(raString); + if (VDBG) log("raString=" + raString + " r=" + r); if (r.mType > ConnectivityManager.MAX_RADIO_TYPE) { loge("Error in radioAttributes - ignoring attempt to define type " + r.mType); continue; @@ -481,6 +525,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { for (String naString : naStrings) { try { NetworkConfig n = new NetworkConfig(naString); + if (VDBG) log("naString=" + naString + " config=" + n); if (n.type > ConnectivityManager.MAX_NETWORK_TYPE) { loge("Error in networkAttributes - ignoring attempt to define type " + n.type); @@ -507,6 +552,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // ignore it - leave the entry null } } + if (VDBG) log("mNetworksDefined=" + mNetworksDefined); mProtectedNetworks = new ArrayList<Integer>(); int[] protectedNetworks = context.getResources().getIntArray( @@ -589,9 +635,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { mTethering = new Tethering(mContext, mNetd, statsService, this, mHandler.getLooper()); - mVpn = new Vpn(mContext, mVpnCallback, mNetd, this); - mVpn.startMonitoring(mContext, mTrackerHandler); - + //set up the listener for user state for creating user VPNs + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_USER_STARTING); + intentFilter.addAction(Intent.ACTION_USER_STOPPING); + mContext.registerReceiverAsUser( + mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null); mClat = new Nat464Xlat(mContext, mNetd, this, mTrackerHandler); try { @@ -609,8 +658,37 @@ public class ConnectivityService extends IConnectivityManager.Stub { mSettingsObserver = new SettingsObserver(mHandler, EVENT_APPLY_GLOBAL_HTTP_PROXY); mSettingsObserver.observe(mContext); - mCaptivePortalTracker = CaptivePortalTracker.makeCaptivePortalTracker(mContext, this); - loadGlobalProxy(); + mDataConnectionStats = new DataConnectionStats(mContext); + mDataConnectionStats.startMonitoring(); + + // start network sampling .. + Intent intent = new Intent(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED, null); + mSampleIntervalElapsedIntent = PendingIntent.getBroadcast(mContext, + SAMPLE_INTERVAL_ELAPSED_REQUEST_CODE, intent, 0); + + mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + setAlarm(DEFAULT_START_SAMPLING_INTERVAL_IN_SECONDS * 1000, mSampleIntervalElapsedIntent); + + IntentFilter filter = new IntentFilter(); + filter.addAction(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED); + mContext.registerReceiver( + new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (action.equals(ACTION_PKT_CNT_SAMPLE_INTERVAL_ELAPSED)) { + mHandler.sendMessage(mHandler.obtainMessage + (EVENT_SAMPLE_INTERVAL_ELAPSED)); + } + } + }, + new IntentFilter(filter)); + + mPacManager = new PacManager(mContext, mHandler, EVENT_PROXY_HAS_CHANGED); + + filter = new IntentFilter(); + filter.addAction(CONNECTED_TO_PROVISIONING_NETWORK_ACTION); + mContext.registerReceiver(mProvisioningReceiver, filter); } /** @@ -879,6 +957,45 @@ public class ConnectivityService extends IConnectivityManager.Stub { return getNetworkInfo(mActiveDefaultNetwork, uid); } + /** + * Find the first Provisioning network. + * + * @return NetworkInfo or null if none. + */ + private NetworkInfo getProvisioningNetworkInfo() { + enforceAccessPermission(); + + // Find the first Provisioning Network + NetworkInfo provNi = null; + for (NetworkInfo ni : getAllNetworkInfo()) { + if (ni.isConnectedToProvisioningNetwork()) { + provNi = ni; + break; + } + } + if (DBG) log("getProvisioningNetworkInfo: X provNi=" + provNi); + return provNi; + } + + /** + * Find the first Provisioning network or the ActiveDefaultNetwork + * if there is no Provisioning network + * + * @return NetworkInfo or null if none. + */ + @Override + public NetworkInfo getProvisioningOrActiveNetworkInfo() { + enforceAccessPermission(); + + NetworkInfo provNi = getProvisioningNetworkInfo(); + if (provNi == null) { + final int uid = Binder.getCallingUid(); + provNi = getNetworkInfo(mActiveDefaultNetwork, uid); + } + if (DBG) log("getProvisioningOrActiveNetworkInfo: X provNi=" + provNi); + return provNi; + } + public NetworkInfo getActiveNetworkInfoUnfiltered() { enforceAccessPermission(); if (isNetworkTypeValid(mActiveDefaultNetwork)) { @@ -1241,8 +1358,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { feature); } if (network.reconnect()) { + if (DBG) log("startUsingNetworkFeature X: return APN_REQUEST_STARTED"); return PhoneConstants.APN_REQUEST_STARTED; } else { + if (DBG) log("startUsingNetworkFeature X: return APN_REQUEST_FAILED"); return PhoneConstants.APN_REQUEST_FAILED; } } else { @@ -1254,9 +1373,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { mNetRequestersPids[usedNetworkType].add(currentPid); } } + if (DBG) log("startUsingNetworkFeature X: return -1 unsupported feature."); return -1; } } + if (DBG) log("startUsingNetworkFeature X: return APN_TYPE_NOT_AVAILABLE"); return PhoneConstants.APN_TYPE_NOT_AVAILABLE; } finally { if (DBG) { @@ -1290,11 +1411,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } if (found && u != null) { + if (VDBG) log("stopUsingNetworkFeature: X"); // stop regardless of how many other time this proc had called start return stopUsingNetworkFeature(u, true); } else { // none found! - if (VDBG) log("stopUsingNetworkFeature - not a live request, ignoring"); + if (VDBG) log("stopUsingNetworkFeature: X not a live request, ignoring"); return 1; } } @@ -1461,7 +1583,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { try { InetAddress addr = InetAddress.getByAddress(hostAddress); LinkProperties lp = tracker.getLinkProperties(); - boolean ok = addRouteToAddress(lp, addr); + boolean ok = addRouteToAddress(lp, addr, EXEMPT); if (DBG) log("requestRouteToHostAddress ok=" + ok); return ok; } catch (UnknownHostException e) { @@ -1473,24 +1595,25 @@ public class ConnectivityService extends IConnectivityManager.Stub { return false; } - private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) { - return modifyRoute(p, r, 0, ADD, toDefaultTable); + private boolean addRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable, + boolean exempt) { + return modifyRoute(p, r, 0, ADD, toDefaultTable, exempt); } private boolean removeRoute(LinkProperties p, RouteInfo r, boolean toDefaultTable) { - return modifyRoute(p, r, 0, REMOVE, toDefaultTable); + return modifyRoute(p, r, 0, REMOVE, toDefaultTable, UNEXEMPT); } - private boolean addRouteToAddress(LinkProperties lp, InetAddress addr) { - return modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE); + private boolean addRouteToAddress(LinkProperties lp, InetAddress addr, boolean exempt) { + return modifyRouteToAddress(lp, addr, ADD, TO_DEFAULT_TABLE, exempt); } private boolean removeRouteToAddress(LinkProperties lp, InetAddress addr) { - return modifyRouteToAddress(lp, addr, REMOVE, TO_DEFAULT_TABLE); + return modifyRouteToAddress(lp, addr, REMOVE, TO_DEFAULT_TABLE, UNEXEMPT); } private boolean modifyRouteToAddress(LinkProperties lp, InetAddress addr, boolean doAdd, - boolean toDefaultTable) { + boolean toDefaultTable, boolean exempt) { RouteInfo bestRoute = RouteInfo.selectBestRoute(lp.getAllRoutes(), addr); if (bestRoute == null) { bestRoute = RouteInfo.makeHostRoute(addr, lp.getInterfaceName()); @@ -1505,11 +1628,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { bestRoute = RouteInfo.makeHostRoute(addr, bestRoute.getGateway(), iface); } } - return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable); + return modifyRoute(lp, bestRoute, 0, doAdd, toDefaultTable, exempt); } private boolean modifyRoute(LinkProperties lp, RouteInfo r, int cycleCount, boolean doAdd, - boolean toDefaultTable) { + boolean toDefaultTable, boolean exempt) { if ((lp == null) || (r == null)) { if (DBG) log("modifyRoute got unexpected null: " + lp + ", " + r); return false; @@ -1538,15 +1661,25 @@ public class ConnectivityService extends IConnectivityManager.Stub { bestRoute.getGateway(), ifaceName); } - modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable); + modifyRoute(lp, bestRoute, cycleCount+1, doAdd, toDefaultTable, exempt); } } if (doAdd) { if (VDBG) log("Adding " + r + " for interface " + ifaceName); try { if (toDefaultTable) { - mAddedRoutes.add(r); // only track default table - only one apps can effect - mNetd.addRoute(ifaceName, r); + synchronized (mRoutesLock) { + // only track default table - only one apps can effect + mAddedRoutes.add(r); + mNetd.addRoute(ifaceName, r); + if (exempt) { + LinkAddress dest = r.getDestination(); + if (!mExemptAddresses.contains(dest)) { + mNetd.setHostExemption(dest); + mExemptAddresses.add(dest); + } + } + } } else { mNetd.addSecondaryRoute(ifaceName, r); } @@ -1559,18 +1692,25 @@ public class ConnectivityService extends IConnectivityManager.Stub { // if we remove this one and there are no more like it, then refcount==0 and // we can remove it from the table if (toDefaultTable) { - mAddedRoutes.remove(r); - if (mAddedRoutes.contains(r) == false) { - if (VDBG) log("Removing " + r + " for interface " + ifaceName); - try { - mNetd.removeRoute(ifaceName, r); - } catch (Exception e) { - // never crash - catch them all - if (VDBG) loge("Exception trying to remove a route: " + e); - return false; + synchronized (mRoutesLock) { + mAddedRoutes.remove(r); + if (mAddedRoutes.contains(r) == false) { + if (VDBG) log("Removing " + r + " for interface " + ifaceName); + try { + mNetd.removeRoute(ifaceName, r); + LinkAddress dest = r.getDestination(); + if (mExemptAddresses.contains(dest)) { + mNetd.clearHostExemption(dest); + mExemptAddresses.remove(dest); + } + } catch (Exception e) { + // never crash - catch them all + if (VDBG) loge("Exception trying to remove a route: " + e); + return false; + } + } else { + if (VDBG) log("not removing " + r + " as it's still in use"); } - } else { - if (VDBG) log("not removing " + r + " as it's still in use"); } } else { if (VDBG) log("Removing " + r + " for interface " + ifaceName); @@ -1747,6 +1887,16 @@ public class ConnectivityService extends IConnectivityManager.Stub { "ConnectivityService"); } + private void enforceMarkNetworkSocketPermission() { + //Media server special case + if (Binder.getCallingUid() == Process.MEDIA_UID) { + return; + } + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.MARK_NETWORK_SOCKET, + "ConnectivityService"); + } + /** * Handle a {@code DISCONNECTED} event. If this pertains to the non-active * network, we ignore it. If it is for the active network, we send out a @@ -1852,6 +2002,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ if (mNetConfigs[prevNetType].isDefault()) { if (mActiveDefaultNetwork == prevNetType) { + if (DBG) { + log("tryFailover: set mActiveDefaultNetwork=-1, prevNetType=" + prevNetType); + } mActiveDefaultNetwork = -1; } @@ -1862,6 +2015,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // if (mActiveDefaultNetwork != -1) { // currentPriority = mNetConfigs[mActiveDefaultNetwork].mPriority; // } + for (int checkType=0; checkType <= ConnectivityManager.MAX_NETWORK_TYPE; checkType++) { if (checkType == prevNetType) continue; if (mNetConfigs[checkType] == null) continue; @@ -1876,6 +2030,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // optimization should work and we need to investigate why it doesn't work. // This could be related to how DEACTIVATE_DATA_CALL is reporting its // complete before it is really complete. + // if (!mNetTrackers[checkType].isAvailable()) continue; // if (currentPriority >= mNetConfigs[checkType].mPriority) continue; @@ -2044,6 +2199,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { } void systemReady() { + mCaptivePortalTracker = CaptivePortalTracker.makeCaptivePortalTracker(mContext, this); + loadGlobalProxy(); + synchronized(this) { mSystemReady = true; if (mInitialBroadcast != null) { @@ -2074,10 +2232,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { }; private boolean isNewNetTypePreferredOverCurrentNetType(int type) { - if ((type != mNetworkPreference && - mNetConfigs[mActiveDefaultNetwork].priority > - mNetConfigs[type].priority) || - mNetworkPreference == mActiveDefaultNetwork) return false; + if (((type != mNetworkPreference) + && (mNetConfigs[mActiveDefaultNetwork].priority > mNetConfigs[type].priority)) + || (mNetworkPreference == mActiveDefaultNetwork)) { + return false; + } return true; } @@ -2091,6 +2250,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { final NetworkStateTracker thisNet = mNetTrackers[newNetType]; final String thisIface = thisNet.getLinkProperties().getInterfaceName(); + if (VDBG) { + log("handleConnect: E newNetType=" + newNetType + " thisIface=" + thisIface + + " isFailover" + isFailover); + } + // if this is a default net and other default is running // kill the one not preferred if (mNetConfigs[newNetType].isDefault()) { @@ -2141,6 +2305,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } thisNet.setTeardownRequested(false); updateNetworkSettings(thisNet); + updateMtuSizeSettings(thisNet); handleConnectivityChange(newNetType, false); sendConnectedBroadcastDelayed(info, getConnectivityChangeDelay()); @@ -2172,15 +2337,26 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } + if (DBG) log("handleCaptivePortalTrackerCheck: call captivePortalCheckComplete ni=" + info); thisNet.captivePortalCheckComplete(); } /** @hide */ + @Override public void captivePortalCheckComplete(NetworkInfo info) { enforceConnectivityInternalPermission(); + if (DBG) log("captivePortalCheckComplete: ni=" + info); mNetTrackers[info.getType()].captivePortalCheckComplete(); } + /** @hide */ + @Override + public void captivePortalCheckCompleted(NetworkInfo info, boolean isCaptivePortal) { + enforceConnectivityInternalPermission(); + if (DBG) log("captivePortalCheckCompleted: ni=" + info + " captive=" + isCaptivePortal); + mNetTrackers[info.getType()].captivePortalCheckCompleted(isCaptivePortal); + } + /** * Setup data activity tracking for the given network interface. * @@ -2241,6 +2417,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { */ private void handleConnectivityChange(int netType, boolean doReset) { int resetMask = doReset ? NetworkUtils.RESET_ALL_ADDRESSES : 0; + boolean exempt = ConnectivityManager.isNetworkTypeExempt(netType); + if (VDBG) { + log("handleConnectivityChange: netType=" + netType + " doReset=" + doReset + + " resetMask=" + resetMask); + } /* * If a non-default network is enabled, add the host routes that @@ -2305,10 +2486,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } mCurrentLinkProperties[netType] = newLp; - boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault()); + boolean resetDns = updateRoutes(newLp, curLp, mNetConfigs[netType].isDefault(), exempt); if (resetMask != 0 || resetDns) { + if (VDBG) log("handleConnectivityChange: resetting"); if (curLp != null) { + if (VDBG) log("handleConnectivityChange: resetting curLp=" + curLp); for (String iface : curLp.getAllInterfaceNames()) { if (TextUtils.isEmpty(iface) == false) { if (resetMask != 0) { @@ -2318,7 +2501,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Tell VPN the interface is down. It is a temporary // but effective fix to make VPN aware of the change. if ((resetMask & NetworkUtils.RESET_IPV4_ADDRESSES) != 0) { - mVpn.interfaceStatusChanged(iface, false); + synchronized(mVpns) { + for (int i = 0; i < mVpns.size(); i++) { + mVpns.valueAt(i).interfaceStatusChanged(iface, false); + } + } } } if (resetDns) { @@ -2341,6 +2528,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Update 464xlat state. NetworkStateTracker tracker = mNetTrackers[netType]; if (mClat.requiresClat(netType, tracker)) { + // If the connection was previously using clat, but is not using it now, stop the clat // daemon. Normally, this happens automatically when the connection disconnects, but if // the disconnect is not reported, or if the connection's LinkProperties changed for @@ -2377,13 +2565,13 @@ public class ConnectivityService extends IConnectivityManager.Stub { * returns a boolean indicating the routes changed */ private boolean updateRoutes(LinkProperties newLp, LinkProperties curLp, - boolean isLinkDefault) { + boolean isLinkDefault, boolean exempt) { Collection<RouteInfo> routesToAdd = null; CompareResult<InetAddress> dnsDiff = new CompareResult<InetAddress>(); CompareResult<RouteInfo> routeDiff = new CompareResult<RouteInfo>(); if (curLp != null) { // check for the delta between the current set and the new - routeDiff = curLp.compareRoutes(newLp); + routeDiff = curLp.compareAllRoutes(newLp); dnsDiff = curLp.compareDnses(newLp); } else if (newLp != null) { routeDiff.added = newLp.getAllRoutes(); @@ -2394,6 +2582,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { for (RouteInfo r : routeDiff.removed) { if (isLinkDefault || ! r.isDefaultRoute()) { + if (VDBG) log("updateRoutes: default remove route r=" + r); removeRoute(curLp, r, TO_DEFAULT_TABLE); } if (isLinkDefault == false) { @@ -2413,7 +2602,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } if (newLp != null) { for (InetAddress newDns : newLp.getDnses()) { - addRouteToAddress(newLp, newDns); + addRouteToAddress(newLp, newDns, exempt); } } } else { @@ -2422,28 +2611,30 @@ public class ConnectivityService extends IConnectivityManager.Stub { removeRouteToAddress(curLp, oldDns); } for (InetAddress newDns : dnsDiff.added) { - addRouteToAddress(newLp, newDns); + addRouteToAddress(newLp, newDns, exempt); } } } for (RouteInfo r : routeDiff.added) { if (isLinkDefault || ! r.isDefaultRoute()) { - addRoute(newLp, r, TO_DEFAULT_TABLE); + addRoute(newLp, r, TO_DEFAULT_TABLE, exempt); } else { // add to a secondary route table - addRoute(newLp, r, TO_SECONDARY_TABLE); + addRoute(newLp, r, TO_SECONDARY_TABLE, UNEXEMPT); // many radios add a default route even when we don't want one. // remove the default route unless somebody else has asked for it String ifaceName = newLp.getInterfaceName(); - if (TextUtils.isEmpty(ifaceName) == false && mAddedRoutes.contains(r) == false) { - if (VDBG) log("Removing " + r + " for interface " + ifaceName); - try { - mNetd.removeRoute(ifaceName, r); - } catch (Exception e) { - // never crash - catch them all - if (DBG) loge("Exception trying to remove a route: " + e); + synchronized (mRoutesLock) { + if (!TextUtils.isEmpty(ifaceName) && !mAddedRoutes.contains(r)) { + if (VDBG) log("Removing " + r + " for interface " + ifaceName); + try { + mNetd.removeRoute(ifaceName, r); + } catch (Exception e) { + // never crash - catch them all + if (DBG) loge("Exception trying to remove a route: " + e); + } } } } @@ -2452,13 +2643,33 @@ public class ConnectivityService extends IConnectivityManager.Stub { return routesChanged; } - /** + * Reads the network specific MTU size from reources. + * and set it on it's iface. + */ + private void updateMtuSizeSettings(NetworkStateTracker nt) { + final String iface = nt.getLinkProperties().getInterfaceName(); + final int mtu = nt.getLinkProperties().getMtu(); + + if (mtu < 68 || mtu > 10000) { + loge("Unexpected mtu value: " + nt); + return; + } + + try { + if (VDBG) log("Setting MTU size: " + iface + ", " + mtu); + mNetd.setMtu(iface, mtu); + } catch (Exception e) { + Slog.e(TAG, "exception in setMtu()" + e); + } + } + + /** * Reads the network specific TCP buffer sizes from SystemProperties * net.tcp.buffersize.[default|wifi|umts|edge|gprs] and set them for system * wide use */ - private void updateNetworkSettings(NetworkStateTracker nt) { + private void updateNetworkSettings(NetworkStateTracker nt) { String key = nt.getTcpBufferSizesPropName(); String bufferSizes = key == null ? null : SystemProperties.get(key); @@ -2480,7 +2691,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - /** + /** * Writes TCP buffer sizes to /sys/kernel/ipv4/tcp_[r/w]mem_[min/def/max] * which maps to /proc/sys/net/ipv4/tcp_rmem and tcpwmem * @@ -2563,7 +2774,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Caller must grab mDnsLock. private void updateDnsLocked(String network, String iface, - Collection<InetAddress> dnses, String domains) { + Collection<InetAddress> dnses, String domains, boolean defaultDns) { int last = 0; if (dnses.size() == 0 && mDefaultDns != null) { dnses = new ArrayList(); @@ -2575,7 +2786,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { try { mNetd.setDnsServersForInterface(iface, NetworkUtils.makeStrings(dnses), domains); - mNetd.setDefaultInterfaceForDns(iface); + if (defaultDns) { + mNetd.setDefaultInterfaceForDns(iface); + } + for (InetAddress dns : dnses) { ++last; String key = "net.dns" + last; @@ -2588,7 +2802,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { } mNumDnsEntries = last; } catch (Exception e) { - if (DBG) loge("exception setting default dns interface: " + e); + loge("exception setting default dns interface: " + e); } } @@ -2602,9 +2816,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (mNetConfigs[netType].isDefault()) { String network = nt.getNetworkInfo().getTypeName(); synchronized (mDnsLock) { - if (!mDnsOverridden) { - updateDnsLocked(network, p.getInterfaceName(), dnses, p.getDomains()); - } + updateDnsLocked(network, p.getInterfaceName(), dnses, p.getDomains(), true); } } else { try { @@ -2728,27 +2940,33 @@ public class ConnectivityService extends IConnectivityManager.Stub { public void handleMessage(Message msg) { NetworkInfo info; switch (msg.what) { - case NetworkStateTracker.EVENT_STATE_CHANGED: + case NetworkStateTracker.EVENT_STATE_CHANGED: { info = (NetworkInfo) msg.obj; - int type = info.getType(); NetworkInfo.State state = info.getState(); if (VDBG || (state == NetworkInfo.State.CONNECTED) || - (state == NetworkInfo.State.DISCONNECTED)) { + (state == NetworkInfo.State.DISCONNECTED) || + (state == NetworkInfo.State.SUSPENDED)) { log("ConnectivityChange for " + info.getTypeName() + ": " + state + "/" + info.getDetailedState()); } - // After booting we'll check once for mobile provisioning - // if we've provisioned by and connected. - if (!mFirstProvisioningCheckStarted + // Since mobile has the notion of a network/apn that can be used for + // provisioning we need to check every time we're connected as + // CaptiveProtalTracker won't detected it because DCT doesn't report it + // as connected as ACTION_ANY_DATA_CONNECTION_STATE_CHANGED instead its + // reported as ACTION_DATA_CONNECTION_CONNECTED_TO_PROVISIONING_APN. Which + // is received by MDST and sent here as EVENT_STATE_CHANGED. + if (ConnectivityManager.isNetworkTypeMobile(info.getType()) && (0 != Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0)) - && (state == NetworkInfo.State.CONNECTED)) { - log("check provisioning after booting"); - mFirstProvisioningCheckStarted = true; - checkMobileProvisioning(true, CheckMp.MAX_TIMEOUT_MS, null); + && (((state == NetworkInfo.State.CONNECTED) + && (info.getType() == ConnectivityManager.TYPE_MOBILE)) + || info.isConnectedToProvisioningNetwork())) { + log("ConnectivityChange checkMobileProvisioning for" + + " TYPE_MOBILE or ProvisioningNetwork"); + checkMobileProvisioning(CheckMp.MAX_TIMEOUT_MS); } EventLogTags.writeConnectivityStateChanged( @@ -2760,6 +2978,29 @@ public class ConnectivityService extends IConnectivityManager.Stub { } else if (info.getDetailedState() == DetailedState.CAPTIVE_PORTAL_CHECK) { handleCaptivePortalTrackerCheck(info); + } else if (info.isConnectedToProvisioningNetwork()) { + /** + * TODO: Create ConnectivityManager.TYPE_MOBILE_PROVISIONING + * for now its an in between network, its a network that + * is actually a default network but we don't want it to be + * announced as such to keep background applications from + * trying to use it. It turns out that some still try so we + * take the additional step of clearing any default routes + * to the link that may have incorrectly setup by the lower + * levels. + */ + LinkProperties lp = getLinkProperties(info.getType()); + if (DBG) { + log("EVENT_STATE_CHANGED: connected to provisioning network, lp=" + lp); + } + + // Clear any default routes setup by the radio so + // any activity by applications trying to use this + // connection will fail until the provisioning network + // is enabled. + for (RouteInfo r : lp.getRoutes()) { + removeRoute(lp, r, TO_DEFAULT_TABLE); + } } else if (state == NetworkInfo.State.DISCONNECTED) { handleDisconnect(info); } else if (state == NetworkInfo.State.SUSPENDED) { @@ -2778,18 +3019,21 @@ public class ConnectivityService extends IConnectivityManager.Stub { mLockdownTracker.onNetworkInfoChanged(info); } break; - case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: + } + case NetworkStateTracker.EVENT_CONFIGURATION_CHANGED: { info = (NetworkInfo) msg.obj; // TODO: Temporary allowing network configuration // change not resetting sockets. // @see bug/4455071 handleConnectivityChange(info.getType(), false); break; - case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: + } + case NetworkStateTracker.EVENT_NETWORK_SUBTYPE_CHANGED: { info = (NetworkInfo) msg.obj; - type = info.getType(); + int type = info.getType(); updateNetworkSettings(mNetTrackers[type]); break; + } } } } @@ -2803,7 +3047,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { public void handleMessage(Message msg) { NetworkInfo info; switch (msg.what) { - case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: + case EVENT_CLEAR_NET_TRANSITION_WAKELOCK: { String causedBy = null; synchronized (ConnectivityService.this) { if (msg.arg1 == mNetTransitionWakeLockSerialNumber && @@ -2816,56 +3060,44 @@ public class ConnectivityService extends IConnectivityManager.Stub { log("NetTransition Wakelock for " + causedBy + " released by timeout"); } break; - case EVENT_RESTORE_DEFAULT_NETWORK: + } + case EVENT_RESTORE_DEFAULT_NETWORK: { FeatureUser u = (FeatureUser)msg.obj; u.expire(); break; - case EVENT_INET_CONDITION_CHANGE: - { + } + case EVENT_INET_CONDITION_CHANGE: { int netType = msg.arg1; int condition = msg.arg2; handleInetConditionChange(netType, condition); break; } - case EVENT_INET_CONDITION_HOLD_END: - { + case EVENT_INET_CONDITION_HOLD_END: { int netType = msg.arg1; int sequence = msg.arg2; handleInetConditionHoldEnd(netType, sequence); break; } - case EVENT_SET_NETWORK_PREFERENCE: - { + case EVENT_SET_NETWORK_PREFERENCE: { int preference = msg.arg1; handleSetNetworkPreference(preference); break; } - case EVENT_SET_MOBILE_DATA: - { + case EVENT_SET_MOBILE_DATA: { boolean enabled = (msg.arg1 == ENABLED); handleSetMobileData(enabled); break; } - case EVENT_APPLY_GLOBAL_HTTP_PROXY: - { + case EVENT_APPLY_GLOBAL_HTTP_PROXY: { handleDeprecatedGlobalHttpProxy(); break; } - case EVENT_SET_DEPENDENCY_MET: - { + case EVENT_SET_DEPENDENCY_MET: { boolean met = (msg.arg1 == ENABLED); handleSetDependencyMet(msg.arg2, met); break; } - case EVENT_RESTORE_DNS: - { - if (mActiveDefaultNetwork != -1) { - handleDnsConfigurationChange(mActiveDefaultNetwork); - } - break; - } - case EVENT_SEND_STICKY_BROADCAST_INTENT: - { + case EVENT_SEND_STICKY_BROADCAST_INTENT: { Intent intent = (Intent)msg.obj; sendStickyBroadcast(intent); break; @@ -2894,6 +3126,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { log("EVENT_ENABLE_FAIL_FAST_MOBILE_DATA: stale arg1:" + msg.arg1 + " != tag:" + tag); } + break; + } + case EVENT_SAMPLE_INTERVAL_ELAPSED: { + handleNetworkSamplingTimeout(); + break; + } + case EVENT_PROXY_HAS_CHANGED: { + handleApplyDefaultProxy((ProxyProperties)msg.obj); + break; } } } @@ -2981,12 +3222,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { return mTethering.getTetheredIfaces(); } - @Override - public String[] getTetheredIfacePairs() { - enforceTetherAccessPermission(); - return mTethering.getTetheredIfacePairs(); - } - public String[] getTetheringErroredIfaces() { enforceTetherAccessPermission(); return mTethering.getErroredIfaces(); @@ -3122,13 +3357,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { // of proxy info to all the JVMs. // enforceAccessPermission(); synchronized (mProxyLock) { - if (mGlobalProxy != null) return mGlobalProxy; - return (mDefaultProxyDisabled ? null : mDefaultProxy); + ProxyProperties ret = mGlobalProxy; + if ((ret == null) && !mDefaultProxyDisabled) ret = mDefaultProxy; + return ret; } } public void setGlobalProxy(ProxyProperties proxyProperties) { enforceConnectivityInternalPermission(); + synchronized (mProxyLock) { if (proxyProperties == mGlobalProxy) return; if (proxyProperties != null && proxyProperties.equals(mGlobalProxy)) return; @@ -3137,7 +3374,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { String host = ""; int port = 0; String exclList = ""; - if (proxyProperties != null && !TextUtils.isEmpty(proxyProperties.getHost())) { + String pacFileUrl = ""; + if (proxyProperties != null && (!TextUtils.isEmpty(proxyProperties.getHost()) || + !TextUtils.isEmpty(proxyProperties.getPacFileUrl()))) { if (!proxyProperties.isValid()) { if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString()); @@ -3147,6 +3386,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { host = mGlobalProxy.getHost(); port = mGlobalProxy.getPort(); exclList = mGlobalProxy.getExclusionList(); + if (proxyProperties.getPacFileUrl() != null) { + pacFileUrl = proxyProperties.getPacFileUrl(); + } } else { mGlobalProxy = null; } @@ -3157,6 +3399,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { Settings.Global.putInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, port); Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST, exclList); + Settings.Global.putString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC, pacFileUrl); } finally { Binder.restoreCallingIdentity(token); } @@ -3174,8 +3417,14 @@ public class ConnectivityService extends IConnectivityManager.Stub { int port = Settings.Global.getInt(res, Settings.Global.GLOBAL_HTTP_PROXY_PORT, 0); String exclList = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_EXCLUSION_LIST); - if (!TextUtils.isEmpty(host)) { - ProxyProperties proxyProperties = new ProxyProperties(host, port, exclList); + String pacFileUrl = Settings.Global.getString(res, Settings.Global.GLOBAL_HTTP_PROXY_PAC); + if (!TextUtils.isEmpty(host) || !TextUtils.isEmpty(pacFileUrl)) { + ProxyProperties proxyProperties; + if (!TextUtils.isEmpty(pacFileUrl)) { + proxyProperties = new ProxyProperties(pacFileUrl); + } else { + proxyProperties = new ProxyProperties(host, port, exclList); + } if (!proxyProperties.isValid()) { if (DBG) log("Invalid proxy properties, ignoring: " + proxyProperties.toString()); return; @@ -3198,7 +3447,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } private void handleApplyDefaultProxy(ProxyProperties proxy) { - if (proxy != null && TextUtils.isEmpty(proxy.getHost())) { + if (proxy != null && TextUtils.isEmpty(proxy.getHost()) + && TextUtils.isEmpty(proxy.getPacFileUrl())) { proxy = null; } synchronized (mProxyLock) { @@ -3222,6 +3472,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { Settings.Global.HTTP_PROXY); if (!TextUtils.isEmpty(proxy)) { String data[] = proxy.split(":"); + if (data.length == 0) { + return; + } + String proxyHost = data[0]; int proxyPort = 8080; if (data.length > 1) { @@ -3238,6 +3492,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { private void sendProxyBroadcast(ProxyProperties proxy) { if (proxy == null) proxy = new ProxyProperties("", 0, ""); + if (mPacManager.setCurrentProxyScriptUrl(proxy)) return; if (DBG) log("sending Proxy Broadcast for " + proxy); Intent intent = new Intent(Proxy.PROXY_CHANGE_ACTION); intent.addFlags(Intent.FLAG_RECEIVER_REPLACE_PENDING | @@ -3332,8 +3587,12 @@ public class ConnectivityService extends IConnectivityManager.Stub { throwIfLockdownEnabled(); try { int type = mActiveDefaultNetwork; + int user = UserHandle.getUserId(Binder.getCallingUid()); if (ConnectivityManager.isNetworkTypeValid(type) && mNetTrackers[type] != null) { - mVpn.protect(socket, mNetTrackers[type].getLinkProperties().getInterfaceName()); + synchronized(mVpns) { + mVpns.get(user).protect(socket, + mNetTrackers[type].getLinkProperties().getInterfaceName()); + } return true; } } catch (Exception e) { @@ -3357,7 +3616,27 @@ public class ConnectivityService extends IConnectivityManager.Stub { @Override public boolean prepareVpn(String oldPackage, String newPackage) { throwIfLockdownEnabled(); - return mVpn.prepare(oldPackage, newPackage); + int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized(mVpns) { + return mVpns.get(user).prepare(oldPackage, newPackage); + } + } + + @Override + public void markSocketAsUser(ParcelFileDescriptor socket, int uid) { + enforceMarkNetworkSocketPermission(); + final long token = Binder.clearCallingIdentity(); + try { + int mark = mNetd.getMarkForUid(uid); + // Clear the mark on the socket if no mark is needed to prevent socket reuse issues + if (mark == -1) { + mark = 0; + } + NetworkUtils.markSocket(socket.getFd(), mark); + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(token); + } } /** @@ -3370,7 +3649,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { @Override public ParcelFileDescriptor establishVpn(VpnConfig config) { throwIfLockdownEnabled(); - return mVpn.establish(config); + int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized(mVpns) { + return mVpns.get(user).establish(config); + } } /** @@ -3384,7 +3666,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { if (egress == null) { throw new IllegalStateException("Missing active network connection"); } - mVpn.startLegacyVpn(profile, mKeyStore, egress); + int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized(mVpns) { + mVpns.get(user).startLegacyVpn(profile, mKeyStore, egress); + } } /** @@ -3396,7 +3681,24 @@ public class ConnectivityService extends IConnectivityManager.Stub { @Override public LegacyVpnInfo getLegacyVpnInfo() { throwIfLockdownEnabled(); - return mVpn.getLegacyVpnInfo(); + int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized(mVpns) { + return mVpns.get(user).getLegacyVpnInfo(); + } + } + + /** + * Returns the information of the ongoing VPN. This method is used by VpnDialogs and + * not available in ConnectivityManager. + * Permissions are checked in Vpn class. + * @hide + */ + @Override + public VpnConfig getVpnConfig() { + int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized(mVpns) { + return mVpns.get(user).getVpnConfig(); + } } /** @@ -3417,7 +3719,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { mHandler.obtainMessage(EVENT_VPN_STATE_CHANGED, info).sendToTarget(); } - public void override(List<String> dnsServers, List<String> searchDomains) { + public void override(String iface, List<String> dnsServers, List<String> searchDomains) { if (dnsServers == null) { restore(); return; @@ -3449,8 +3751,7 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Apply DNS changes. synchronized (mDnsLock) { - updateDnsLocked("VPN", "VPN", addresses, domains); - mDnsOverridden = true; + updateDnsLocked("VPN", iface, addresses, domains, false); } // Temporarily disable the default proxy (not global). @@ -3465,12 +3766,6 @@ public class ConnectivityService extends IConnectivityManager.Stub { } public void restore() { - synchronized (mDnsLock) { - if (mDnsOverridden) { - mDnsOverridden = false; - mHandler.sendEmptyMessage(EVENT_RESTORE_DNS); - } - } synchronized (mProxyLock) { mDefaultProxyDisabled = false; if (mGlobalProxy == null && mDefaultProxy != null) { @@ -3478,6 +3773,69 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } } + + public void protect(ParcelFileDescriptor socket) { + try { + final int mark = mNetd.getMarkForProtect(); + NetworkUtils.markSocket(socket.getFd(), mark); + } catch (RemoteException e) { + } + } + + public void setRoutes(String interfaze, List<RouteInfo> routes) { + for (RouteInfo route : routes) { + try { + mNetd.setMarkedForwardingRoute(interfaze, route); + } catch (RemoteException e) { + } + } + } + + public void setMarkedForwarding(String interfaze) { + try { + mNetd.setMarkedForwarding(interfaze); + } catch (RemoteException e) { + } + } + + public void clearMarkedForwarding(String interfaze) { + try { + mNetd.clearMarkedForwarding(interfaze); + } catch (RemoteException e) { + } + } + + public void addUserForwarding(String interfaze, int uid, boolean forwardDns) { + int uidStart = uid * UserHandle.PER_USER_RANGE; + int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1; + addUidForwarding(interfaze, uidStart, uidEnd, forwardDns); + } + + public void clearUserForwarding(String interfaze, int uid, boolean forwardDns) { + int uidStart = uid * UserHandle.PER_USER_RANGE; + int uidEnd = uidStart + UserHandle.PER_USER_RANGE - 1; + clearUidForwarding(interfaze, uidStart, uidEnd, forwardDns); + } + + public void addUidForwarding(String interfaze, int uidStart, int uidEnd, + boolean forwardDns) { + try { + mNetd.setUidRangeRoute(interfaze,uidStart, uidEnd); + if (forwardDns) mNetd.setDnsInterfaceForUidRange(interfaze, uidStart, uidEnd); + } catch (RemoteException e) { + } + + } + + public void clearUidForwarding(String interfaze, int uidStart, int uidEnd, + boolean forwardDns) { + try { + mNetd.clearUidRangeRoute(interfaze, uidStart, uidEnd); + if (forwardDns) mNetd.clearDnsInterfaceForUidRange(uidStart, uidEnd); + } catch (RemoteException e) { + } + + } } @Override @@ -3498,7 +3856,11 @@ public class ConnectivityService extends IConnectivityManager.Stub { final String profileName = new String(mKeyStore.get(Credentials.LOCKDOWN_VPN)); final VpnProfile profile = VpnProfile.decode( profileName, mKeyStore.get(Credentials.VPN + profileName)); - setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpn, profile)); + int user = UserHandle.getUserId(Binder.getCallingUid()); + synchronized(mVpns) { + setLockdownTracker(new LockdownVpnTracker(mContext, mNetd, this, mVpns.get(user), + profile)); + } } else { setLockdownTracker(null); } @@ -3578,72 +3940,124 @@ public class ConnectivityService extends IConnectivityManager.Stub { enabled)); } - @Override - public int checkMobileProvisioning(boolean sendNotification, int suggestedTimeOutMs, - final ResultReceiver resultReceiver) { - log("checkMobileProvisioning: E sendNotification=" + sendNotification - + " suggestedTimeOutMs=" + suggestedTimeOutMs - + " resultReceiver=" + resultReceiver); - enforceChangePermission(); + private boolean isMobileDataStateTrackerReady() { + MobileDataStateTracker mdst = + (MobileDataStateTracker) mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI]; + return (mdst != null) && (mdst.isReady()); + } - mFirstProvisioningCheckStarted = true; + /** + * The ResultReceiver resultCode for checkMobileProvisioning (CMP_RESULT_CODE) + */ - int timeOutMs = suggestedTimeOutMs; - if (suggestedTimeOutMs > CheckMp.MAX_TIMEOUT_MS) { - timeOutMs = CheckMp.MAX_TIMEOUT_MS; - } + /** + * No connection was possible to the network. + * This is NOT a warm sim. + */ + private static final int CMP_RESULT_CODE_NO_CONNECTION = 0; - // Check that mobile networks are supported - if (!isNetworkSupported(ConnectivityManager.TYPE_MOBILE) - || !isNetworkSupported(ConnectivityManager.TYPE_MOBILE_HIPRI)) { - log("checkMobileProvisioning: X no mobile network"); - if (resultReceiver != null) { - resultReceiver.send(ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION, null); - } - return timeOutMs; - } + /** + * A connection was made to the internet, all is well. + * This is NOT a warm sim. + */ + private static final int CMP_RESULT_CODE_CONNECTABLE = 1; + + /** + * A connection was made but no dns server was available to resolve a name to address. + * This is NOT a warm sim since provisioning network is supported. + */ + private static final int CMP_RESULT_CODE_NO_DNS = 2; + + /** + * A connection was made but could not open a TCP connection. + * This is NOT a warm sim since provisioning network is supported. + */ + private static final int CMP_RESULT_CODE_NO_TCP_CONNECTION = 3; + + /** + * A connection was made but there was a redirection, we appear to be in walled garden. + * This is an indication of a warm sim on a mobile network such as T-Mobile. + */ + private static final int CMP_RESULT_CODE_REDIRECTED = 4; + + /** + * The mobile network is a provisioning network. + * This is an indication of a warm sim on a mobile network such as AT&T. + */ + private static final int CMP_RESULT_CODE_PROVISIONING_NETWORK = 5; + + private AtomicBoolean mIsCheckingMobileProvisioning = new AtomicBoolean(false); + + @Override + public int checkMobileProvisioning(int suggestedTimeOutMs) { + int timeOutMs = -1; + if (DBG) log("checkMobileProvisioning: E suggestedTimeOutMs=" + suggestedTimeOutMs); + enforceConnectivityInternalPermission(); final long token = Binder.clearCallingIdentity(); try { + timeOutMs = suggestedTimeOutMs; + if (suggestedTimeOutMs > CheckMp.MAX_TIMEOUT_MS) { + timeOutMs = CheckMp.MAX_TIMEOUT_MS; + } + + // Check that mobile networks are supported + if (!isNetworkSupported(ConnectivityManager.TYPE_MOBILE) + || !isNetworkSupported(ConnectivityManager.TYPE_MOBILE_HIPRI)) { + if (DBG) log("checkMobileProvisioning: X no mobile network"); + return timeOutMs; + } + + // If we're already checking don't do it again + // TODO: Add a queue of results... + if (mIsCheckingMobileProvisioning.getAndSet(true)) { + if (DBG) log("checkMobileProvisioning: X already checking ignore for the moment"); + return timeOutMs; + } + + // Start off with mobile notification off + setProvNotificationVisible(false, ConnectivityManager.TYPE_MOBILE_HIPRI, null, null); + CheckMp checkMp = new CheckMp(mContext, this); CheckMp.CallBack cb = new CheckMp.CallBack() { @Override void onComplete(Integer result) { - log("CheckMp.onComplete: result=" + result); - if (resultReceiver != null) { - log("CheckMp.onComplete: send result"); - resultReceiver.send(result, null); - } + if (DBG) log("CheckMp.onComplete: result=" + result); NetworkInfo ni = mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI].getNetworkInfo(); switch(result) { - case ConnectivityManager.CMP_RESULT_CODE_CONNECTABLE: - case ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION: { - log("CheckMp.onComplete: ignore, connected or no connection"); + case CMP_RESULT_CODE_CONNECTABLE: + case CMP_RESULT_CODE_NO_CONNECTION: + case CMP_RESULT_CODE_NO_DNS: + case CMP_RESULT_CODE_NO_TCP_CONNECTION: { + if (DBG) log("CheckMp.onComplete: ignore, connected or no connection"); break; } - case ConnectivityManager.CMP_RESULT_CODE_REDIRECTED: { - log("CheckMp.onComplete: warm sim"); + case CMP_RESULT_CODE_REDIRECTED: { + if (DBG) log("CheckMp.onComplete: warm sim"); String url = getMobileProvisioningUrl(); if (TextUtils.isEmpty(url)) { url = getMobileRedirectedProvisioningUrl(); } if (TextUtils.isEmpty(url) == false) { - log("CheckMp.onComplete: warm sim (redirected), url=" + url); - setNotificationVisible(true, ni, url); + if (DBG) log("CheckMp.onComplete: warm (redirected), url=" + url); + setProvNotificationVisible(true, + ConnectivityManager.TYPE_MOBILE_HIPRI, ni.getExtraInfo(), + url); } else { - log("CheckMp.onComplete: warm sim (redirected), no url"); + if (DBG) log("CheckMp.onComplete: warm (redirected), no url"); } break; } - case ConnectivityManager.CMP_RESULT_CODE_NO_DNS: - case ConnectivityManager.CMP_RESULT_CODE_NO_TCP_CONNECTION: { + case CMP_RESULT_CODE_PROVISIONING_NETWORK: { String url = getMobileProvisioningUrl(); if (TextUtils.isEmpty(url) == false) { - log("CheckMp.onComplete: warm sim (no dns/tcp), url=" + url); - setNotificationVisible(true, ni, url); + if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), url=" + url); + setProvNotificationVisible(true, + ConnectivityManager.TYPE_MOBILE_HIPRI, ni.getExtraInfo(), + url); } else { - log("CheckMp.onComplete: warm sim (no dns/tcp), no url"); + if (DBG) log("CheckMp.onComplete: warm (no dns/tcp), no url"); } break; } @@ -3652,16 +4066,16 @@ public class ConnectivityService extends IConnectivityManager.Stub { break; } } + mIsCheckingMobileProvisioning.set(false); } }; CheckMp.Params params = new CheckMp.Params(checkMp.getDefaultUrl(), timeOutMs, cb); - log("checkMobileProvisioning: params=" + params); - setNotificationVisible(false, null, null); + if (DBG) log("checkMobileProvisioning: params=" + params); checkMp.execute(params); } finally { Binder.restoreCallingIdentity(token); - log("checkMobileProvisioning: X"); + if (DBG) log("checkMobileProvisioning: X"); } return timeOutMs; } @@ -3733,27 +4147,72 @@ public class ConnectivityService extends IConnectivityManager.Stub { * a known address that fetches the data we expect. */ private synchronized Integer isMobileOk(Params params) { - Integer result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION; + Integer result = CMP_RESULT_CODE_NO_CONNECTION; Uri orgUri = Uri.parse(params.mUrl); Random rand = new Random(); mParams = params; + if (mCs.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) { + result = CMP_RESULT_CODE_NO_CONNECTION; + log("isMobileOk: X not mobile capable result=" + result); + return result; + } + + // See if we've already determined we've got a provisioning connection, + // if so we don't need to do anything active. + MobileDataStateTracker mdstDefault = (MobileDataStateTracker) + mCs.mNetTrackers[ConnectivityManager.TYPE_MOBILE]; + boolean isDefaultProvisioning = mdstDefault.isProvisioningNetwork(); + log("isMobileOk: isDefaultProvisioning=" + isDefaultProvisioning); + + MobileDataStateTracker mdstHipri = (MobileDataStateTracker) + mCs.mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI]; + boolean isHipriProvisioning = mdstHipri.isProvisioningNetwork(); + log("isMobileOk: isHipriProvisioning=" + isHipriProvisioning); + + if (isDefaultProvisioning || isHipriProvisioning) { + result = CMP_RESULT_CODE_PROVISIONING_NETWORK; + log("isMobileOk: X default || hipri is provisioning result=" + result); + return result; + } + try { - if (mCs.isNetworkSupported(ConnectivityManager.TYPE_MOBILE) == false) { - log("isMobileOk: not mobile capable"); - result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION; - return result; + // Continue trying to connect until time has run out + long endTime = SystemClock.elapsedRealtime() + params.mTimeOutMs; + + if (!mCs.isMobileDataStateTrackerReady()) { + // Wait for MobileDataStateTracker to be ready. + if (DBG) log("isMobileOk: mdst is not ready"); + while(SystemClock.elapsedRealtime() < endTime) { + if (mCs.isMobileDataStateTrackerReady()) { + // Enable fail fast as we'll do retries here and use a + // hipri connection so the default connection stays active. + if (DBG) log("isMobileOk: mdst ready, enable fail fast of mobile data"); + mCs.setEnableFailFastMobileData(DctConstants.ENABLED); + break; + } + sleep(1); + } } - // Enable fail fast as we'll do retries here and use a - // hipri connection so the default connection stays active. log("isMobileOk: start hipri url=" + params.mUrl); - mCs.setEnableFailFastMobileData(DctConstants.ENABLED); - mCs.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, - Phone.FEATURE_ENABLE_HIPRI, new Binder()); + + // First wait until we can start using hipri + Binder binder = new Binder(); + while(SystemClock.elapsedRealtime() < endTime) { + int ret = mCs.startUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, + Phone.FEATURE_ENABLE_HIPRI, binder); + if ((ret == PhoneConstants.APN_ALREADY_ACTIVE) + || (ret == PhoneConstants.APN_REQUEST_STARTED)) { + log("isMobileOk: hipri started"); + break; + } + if (VDBG) log("isMobileOk: hipri not started yet"); + result = CMP_RESULT_CODE_NO_CONNECTION; + sleep(1); + } // Continue trying to connect until time has run out - long endTime = SystemClock.elapsedRealtime() + params.mTimeOutMs; while(SystemClock.elapsedRealtime() < endTime) { try { // Wait for hipri to connect. @@ -3762,13 +4221,26 @@ public class ConnectivityService extends IConnectivityManager.Stub { NetworkInfo.State state = mCs .getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState(); if (state != NetworkInfo.State.CONNECTED) { - log("isMobileOk: not connected ni=" + + if (true/*VDBG*/) { + log("isMobileOk: not connected ni=" + mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI)); + } sleep(1); - result = ConnectivityManager.CMP_RESULT_CODE_NO_CONNECTION; + result = CMP_RESULT_CODE_NO_CONNECTION; continue; } + // Hipri has started check if this is a provisioning url + MobileDataStateTracker mdst = (MobileDataStateTracker) + mCs.mNetTrackers[ConnectivityManager.TYPE_MOBILE_HIPRI]; + if (mdst.isProvisioningNetwork()) { + result = CMP_RESULT_CODE_PROVISIONING_NETWORK; + if (DBG) log("isMobileOk: X isProvisioningNetwork result=" + result); + return result; + } else { + if (DBG) log("isMobileOk: isProvisioningNetwork is false, continue"); + } + // Get of the addresses associated with the url host. We need to use the // address otherwise HttpURLConnection object will use the name to get // the addresses and is will try every address but that will bypass the @@ -3778,8 +4250,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { try { addresses = InetAddress.getAllByName(orgUri.getHost()); } catch (UnknownHostException e) { - log("isMobileOk: UnknownHostException"); - result = ConnectivityManager.CMP_RESULT_CODE_NO_DNS; + result = CMP_RESULT_CODE_NO_DNS; + log("isMobileOk: X UnknownHostException result=" + result); return result; } log("isMobileOk: addresses=" + inetAddressesToString(addresses)); @@ -3787,30 +4259,41 @@ public class ConnectivityService extends IConnectivityManager.Stub { // Get the type of addresses supported by this link LinkProperties lp = mCs.getLinkProperties( ConnectivityManager.TYPE_MOBILE_HIPRI); - boolean linkHasIpv4 = hasIPv4Address(lp); - boolean linkHasIpv6 = hasIPv6Address(lp); + boolean linkHasIpv4 = lp.hasIPv4Address(); + boolean linkHasIpv6 = lp.hasIPv6Address(); log("isMobileOk: linkHasIpv4=" + linkHasIpv4 + " linkHasIpv6=" + linkHasIpv6); - // Loop through at most 3 valid addresses or all of the address or until - // we run out of time - int loops = Math.min(3, addresses.length); - for(int validAddr=0, addrTried=0; - (validAddr < loops) && (addrTried < addresses.length) - && (SystemClock.elapsedRealtime() < endTime); - addrTried ++) { - - // Choose the address at random but make sure its type is supported - InetAddress hostAddr = addresses[rand.nextInt(addresses.length)]; - if (((hostAddr instanceof Inet4Address) && linkHasIpv4) - || ((hostAddr instanceof Inet6Address) && linkHasIpv6)) { - // Valid address, so use it - validAddr += 1; - } else { - // Invalid address so try next address - continue; + final ArrayList<InetAddress> validAddresses = + new ArrayList<InetAddress>(addresses.length); + + for (InetAddress addr : addresses) { + if (((addr instanceof Inet4Address) && linkHasIpv4) || + ((addr instanceof Inet6Address) && linkHasIpv6)) { + validAddresses.add(addr); + } + } + + if (validAddresses.size() == 0) { + return CMP_RESULT_CODE_NO_CONNECTION; + } + + int addrTried = 0; + while (true) { + // Loop through at most 3 valid addresses or until + // we run out of time + if (addrTried++ >= 3) { + log("too many loops tried - giving up"); + break; + } + if (SystemClock.elapsedRealtime() >= endTime) { + log("spend too much time - giving up"); + break; } + InetAddress hostAddr = validAddresses.get(rand.nextInt( + validAddresses.size())); + // Make a route to host so we check the specific interface. if (mCs.requestRouteToHostAddress(ConnectivityManager.TYPE_MOBILE_HIPRI, hostAddr.getAddress())) { @@ -3825,10 +4308,10 @@ public class ConnectivityService extends IConnectivityManager.Stub { } // Rewrite the url to have numeric address to use the specific route. - // I also set the "Connection" to "Close" as by default "Keep-Alive" - // is used which is useless in this case. - URL newUrl = new URL(orgUri.getScheme() + "://" - + hostAddr.getHostAddress() + orgUri.getPath()); + // Add a pointless random query param to fool proxies into not caching. + URL newUrl = new URL(orgUri.getScheme(), + hostAddr.getHostAddress(), + orgUri.getPath() + "?q=" + rand.nextInt(Integer.MAX_VALUE)); log("isMobileOk: newUrl=" + newUrl); HttpURLConnection urlConn = null; @@ -3841,27 +4324,45 @@ public class ConnectivityService extends IConnectivityManager.Stub { urlConn.setReadTimeout(SOCKET_TIMEOUT_MS); urlConn.setUseCaches(false); urlConn.setAllowUserInteraction(false); + // Set the "Connection" to "Close" as by default "Keep-Alive" + // is used which is useless in this case. urlConn.setRequestProperty("Connection", "close"); int responseCode = urlConn.getResponseCode(); + + // For debug display the headers + Map<String, List<String>> headers = urlConn.getHeaderFields(); + log("isMobileOk: headers=" + headers); + + // Close the connection + urlConn.disconnect(); + urlConn = null; + if (responseCode == 204) { - result = ConnectivityManager.CMP_RESULT_CODE_CONNECTABLE; + // Return + result = CMP_RESULT_CODE_CONNECTABLE; + log("isMobileOk: X expected responseCode=" + responseCode + + " result=" + result); + return result; } else { - result = ConnectivityManager.CMP_RESULT_CODE_REDIRECTED; + // Retry to be sure this was redirected, we've gotten + // occasions where a server returned 200 even though + // the device didn't have a "warm" sim. + log("isMobileOk: not expected responseCode=" + responseCode); + // TODO - it would be nice in the single-address case to do + // another DNS resolve here, but flushing the cache is a bit + // heavy-handed. + result = CMP_RESULT_CODE_REDIRECTED; } - log("isMobileOk: connected responseCode=" + responseCode); - urlConn.disconnect(); - urlConn = null; - return result; } catch (Exception e) { log("isMobileOk: HttpURLConnection Exception e=" + e); + result = CMP_RESULT_CODE_NO_TCP_CONNECTION; if (urlConn != null) { urlConn.disconnect(); urlConn = null; } } } - result = ConnectivityManager.CMP_RESULT_CODE_NO_TCP_CONNECTION; - log("isMobileOk: loops|timed out"); + log("isMobileOk: X loops|timed out result=" + result); return result; } catch (Exception e) { log("isMobileOk: Exception e=" + e); @@ -3874,6 +4375,23 @@ public class ConnectivityService extends IConnectivityManager.Stub { mCs.setEnableFailFastMobileData(DctConstants.DISABLED); mCs.stopUsingNetworkFeature(ConnectivityManager.TYPE_MOBILE, Phone.FEATURE_ENABLE_HIPRI); + + // Wait for hipri to disconnect. + long endTime = SystemClock.elapsedRealtime() + 5000; + + while(SystemClock.elapsedRealtime() < endTime) { + NetworkInfo.State state = mCs + .getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI).getState(); + if (state != NetworkInfo.State.DISCONNECTED) { + if (VDBG) { + log("isMobileOk: connected ni=" + + mCs.getNetworkInfo(ConnectivityManager.TYPE_MOBILE_HIPRI)); + } + sleep(1); + continue; + } + } + log("isMobileOk: X result=" + result); } return result; @@ -3934,29 +4452,59 @@ public class ConnectivityService extends IConnectivityManager.Stub { } } - public boolean hasIPv4Address(LinkProperties lp) { - return lp.hasIPv4Address(); + private void log(String s) { + Slog.d(ConnectivityService.TAG, "[" + CHECKMP_TAG + "] " + s); } + } - // Not implemented in LinkProperties, do it here. - public boolean hasIPv6Address(LinkProperties lp) { - for (LinkAddress address : lp.getLinkAddresses()) { - if (address.getAddress() instanceof Inet6Address) { - return true; - } + // TODO: Move to ConnectivityManager and make public? + private static final String CONNECTED_TO_PROVISIONING_NETWORK_ACTION = + "com.android.server.connectivityservice.CONNECTED_TO_PROVISIONING_NETWORK_ACTION"; + + private BroadcastReceiver mProvisioningReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + if (intent.getAction().equals(CONNECTED_TO_PROVISIONING_NETWORK_ACTION)) { + handleMobileProvisioningAction(intent.getStringExtra("EXTRA_URL")); } - return false; } + }; - private void log(String s) { - Slog.d(ConnectivityService.TAG, "[" + CHECKMP_TAG + "] " + s); + private void handleMobileProvisioningAction(String url) { + // Notication mark notification as not visible + setProvNotificationVisible(false, ConnectivityManager.TYPE_MOBILE_HIPRI, null, null); + + // If provisioning network handle as a special case, + // otherwise launch browser with the intent directly. + NetworkInfo ni = getProvisioningNetworkInfo(); + if ((ni != null) && ni.isConnectedToProvisioningNetwork()) { + if (DBG) log("handleMobileProvisioningAction: on provisioning network"); + MobileDataStateTracker mdst = (MobileDataStateTracker) + mNetTrackers[ConnectivityManager.TYPE_MOBILE]; + mdst.enableMobileProvisioning(url); + } else { + if (DBG) log("handleMobileProvisioningAction: on default network"); + Intent newIntent = + new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + newIntent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | + Intent.FLAG_ACTIVITY_NEW_TASK); + try { + mContext.startActivity(newIntent); + } catch (ActivityNotFoundException e) { + loge("handleMobileProvisioningAction: startActivity failed" + e); + } } } private static final String NOTIFICATION_ID = "CaptivePortal.Notification"; + private volatile boolean mIsNotificationVisible = false; - private void setNotificationVisible(boolean visible, NetworkInfo networkInfo, String url) { - log("setNotificationVisible: E visible=" + visible + " ni=" + networkInfo + " url=" + url); + private void setProvNotificationVisible(boolean visible, int networkType, String extraInfo, + String url) { + if (DBG) { + log("setProvNotificationVisible: E visible=" + visible + " networkType=" + networkType + + " extraInfo=" + extraInfo + " url=" + url); + } Resources r = Resources.getSystem(); NotificationManager notificationManager = (NotificationManager) mContext @@ -3966,50 +4514,64 @@ public class ConnectivityService extends IConnectivityManager.Stub { CharSequence title; CharSequence details; int icon; - switch (networkInfo.getType()) { + Intent intent; + Notification notification = new Notification(); + switch (networkType) { case ConnectivityManager.TYPE_WIFI: - log("setNotificationVisible: TYPE_WIFI"); title = r.getString(R.string.wifi_available_sign_in, 0); details = r.getString(R.string.network_available_sign_in_detailed, - networkInfo.getExtraInfo()); + extraInfo); icon = R.drawable.stat_notify_wifi_in_range; + intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | + Intent.FLAG_ACTIVITY_NEW_TASK); + notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0); break; case ConnectivityManager.TYPE_MOBILE: case ConnectivityManager.TYPE_MOBILE_HIPRI: - log("setNotificationVisible: TYPE_MOBILE|HIPRI"); title = r.getString(R.string.network_available_sign_in, 0); // TODO: Change this to pull from NetworkInfo once a printable // name has been added to it details = mTelephonyManager.getNetworkOperatorName(); icon = R.drawable.stat_notify_rssi_in_range; + intent = new Intent(CONNECTED_TO_PROVISIONING_NETWORK_ACTION); + intent.putExtra("EXTRA_URL", url); + intent.setFlags(0); + notification.contentIntent = PendingIntent.getBroadcast(mContext, 0, intent, 0); break; default: - log("setNotificationVisible: other type=" + networkInfo.getType()); title = r.getString(R.string.network_available_sign_in, 0); details = r.getString(R.string.network_available_sign_in_detailed, - networkInfo.getExtraInfo()); + extraInfo); icon = R.drawable.stat_notify_rssi_in_range; + intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); + intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | + Intent.FLAG_ACTIVITY_NEW_TASK); + notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0); break; } - Notification notification = new Notification(); notification.when = 0; notification.icon = icon; notification.flags = Notification.FLAG_AUTO_CANCEL; - Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url)); - intent.setFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT | - Intent.FLAG_ACTIVITY_NEW_TASK); - notification.contentIntent = PendingIntent.getActivity(mContext, 0, intent, 0); notification.tickerText = title; notification.setLatestEventInfo(mContext, title, details, notification.contentIntent); - log("setNotificaitionVisible: notify notificaiton=" + notification); - notificationManager.notify(NOTIFICATION_ID, 1, notification); + try { + notificationManager.notify(NOTIFICATION_ID, networkType, notification); + } catch (NullPointerException npe) { + loge("setNotificaitionVisible: visible notificationManager npe=" + npe); + npe.printStackTrace(); + } } else { - log("setNotificaitionVisible: cancel"); - notificationManager.cancel(NOTIFICATION_ID, 1); + try { + notificationManager.cancel(NOTIFICATION_ID, networkType); + } catch (NullPointerException npe) { + loge("setNotificaitionVisible: cancel notificationManager npe=" + npe); + npe.printStackTrace(); + } } - log("setNotificationVisible: X visible=" + visible + " ni=" + networkInfo + " url=" + url); + mIsNotificationVisible = visible; } /** Location to an updatable file listing carrier provisioning urls. @@ -4103,7 +4665,9 @@ public class ConnectivityService extends IConnectivityManager.Stub { return null; } - private String getMobileRedirectedProvisioningUrl() { + @Override + public String getMobileRedirectedProvisioningUrl() { + enforceConnectivityInternalPermission(); String url = getProvisioningUrlBaseFromFile(REDIRECTED_PROVISIONING); if (TextUtils.isEmpty(url)) { url = mContext.getResources().getString(R.string.mobile_redirected_provisioning_url); @@ -4111,14 +4675,15 @@ public class ConnectivityService extends IConnectivityManager.Stub { return url; } + @Override public String getMobileProvisioningUrl() { enforceConnectivityInternalPermission(); String url = getProvisioningUrlBaseFromFile(PROVISIONING); if (TextUtils.isEmpty(url)) { url = mContext.getResources().getString(R.string.mobile_provisioning_url); - log("getProvisioningUrl: mobile_provisioining_url from resource =" + url); + log("getMobileProvisioningUrl: mobile_provisioining_url from resource =" + url); } else { - log("getProvisioningUrl: mobile_provisioning_url from File =" + url); + log("getMobileProvisioningUrl: mobile_provisioning_url from File =" + url); } // populate the iccid, imei and phone number in the provisioning url. if (!TextUtils.isEmpty(url)) { @@ -4134,4 +4699,152 @@ public class ConnectivityService extends IConnectivityManager.Stub { return url; } + + @Override + public void setProvisioningNotificationVisible(boolean visible, int networkType, + String extraInfo, String url) { + enforceConnectivityInternalPermission(); + setProvNotificationVisible(visible, networkType, extraInfo, url); + } + + @Override + public void setAirplaneMode(boolean enable) { + enforceConnectivityInternalPermission(); + final long ident = Binder.clearCallingIdentity(); + try { + final ContentResolver cr = mContext.getContentResolver(); + Settings.Global.putInt(cr, Settings.Global.AIRPLANE_MODE_ON, enable ? 1 : 0); + Intent intent = new Intent(Intent.ACTION_AIRPLANE_MODE_CHANGED); + intent.putExtra("state", enable); + mContext.sendBroadcast(intent); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void onUserStart(int userId) { + synchronized(mVpns) { + Vpn userVpn = mVpns.get(userId); + if (userVpn != null) { + loge("Starting user already has a VPN"); + return; + } + userVpn = new Vpn(mContext, mVpnCallback, mNetd, this, userId); + mVpns.put(userId, userVpn); + userVpn.startMonitoring(mContext, mTrackerHandler); + } + } + + private void onUserStop(int userId) { + synchronized(mVpns) { + Vpn userVpn = mVpns.get(userId); + if (userVpn == null) { + loge("Stopping user has no VPN"); + return; + } + mVpns.delete(userId); + } + } + + private BroadcastReceiver mUserIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + if (userId == UserHandle.USER_NULL) return; + + if (Intent.ACTION_USER_STARTING.equals(action)) { + onUserStart(userId); + } else if (Intent.ACTION_USER_STOPPING.equals(action)) { + onUserStop(userId); + } + } + }; + + @Override + public LinkQualityInfo getLinkQualityInfo(int networkType) { + enforceAccessPermission(); + if (isNetworkTypeValid(networkType)) { + return mNetTrackers[networkType].getLinkQualityInfo(); + } else { + return null; + } + } + + @Override + public LinkQualityInfo getActiveLinkQualityInfo() { + enforceAccessPermission(); + if (isNetworkTypeValid(mActiveDefaultNetwork)) { + return mNetTrackers[mActiveDefaultNetwork].getLinkQualityInfo(); + } else { + return null; + } + } + + @Override + public LinkQualityInfo[] getAllLinkQualityInfo() { + enforceAccessPermission(); + final ArrayList<LinkQualityInfo> result = Lists.newArrayList(); + for (NetworkStateTracker tracker : mNetTrackers) { + if (tracker != null) { + LinkQualityInfo li = tracker.getLinkQualityInfo(); + if (li != null) { + result.add(li); + } + } + } + + return result.toArray(new LinkQualityInfo[result.size()]); + } + + /* Infrastructure for network sampling */ + + private void handleNetworkSamplingTimeout() { + + log("Sampling interval elapsed, updating statistics .."); + + // initialize list of interfaces .. + Map<String, SamplingDataTracker.SamplingSnapshot> mapIfaceToSample = + new HashMap<String, SamplingDataTracker.SamplingSnapshot>(); + for (NetworkStateTracker tracker : mNetTrackers) { + if (tracker != null) { + String ifaceName = tracker.getNetworkInterfaceName(); + if (ifaceName != null) { + mapIfaceToSample.put(ifaceName, null); + } + } + } + + // Read samples for all interfaces + SamplingDataTracker.getSamplingSnapshots(mapIfaceToSample); + + // process samples for all networks + for (NetworkStateTracker tracker : mNetTrackers) { + if (tracker != null) { + String ifaceName = tracker.getNetworkInterfaceName(); + SamplingDataTracker.SamplingSnapshot ss = mapIfaceToSample.get(ifaceName); + if (ss != null) { + // end the previous sampling cycle + tracker.stopSampling(ss); + // start a new sampling cycle .. + tracker.startSampling(ss); + } + } + } + + log("Done."); + + int samplingIntervalInSeconds = Settings.Global.getInt(mContext.getContentResolver(), + Settings.Global.CONNECTIVITY_SAMPLING_INTERVAL_IN_SECONDS, + DEFAULT_SAMPLING_INTERVAL_IN_SECONDS); + + if (DBG) log("Setting timer for " + String.valueOf(samplingIntervalInSeconds) + "seconds"); + + setAlarm(samplingIntervalInSeconds * 1000, mSampleIntervalElapsedIntent); + } + + void setAlarm(int timeoutInMilliseconds, PendingIntent intent) { + long wakeupTime = SystemClock.elapsedRealtime() + timeoutInMilliseconds; + mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, wakeupTime, intent); + } } diff --git a/services/java/com/android/server/ConsumerIrService.java b/services/java/com/android/server/ConsumerIrService.java new file mode 100644 index 000000000000..783dff111443 --- /dev/null +++ b/services/java/com/android/server/ConsumerIrService.java @@ -0,0 +1,135 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.database.ContentObserver; +import android.hardware.input.InputManager; +import android.hardware.IConsumerIrService; +import android.os.Handler; +import android.os.PowerManager; +import android.os.Process; +import android.os.RemoteException; +import android.os.IBinder; +import android.os.Binder; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.UserHandle; +import android.os.WorkSource; +import android.provider.Settings; +import android.provider.Settings.SettingNotFoundException; +import android.util.Slog; +import android.view.InputDevice; + +import java.lang.RuntimeException; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.ListIterator; + +public class ConsumerIrService extends IConsumerIrService.Stub { + private static final String TAG = "ConsumerIrService"; + + private static final int MAX_XMIT_TIME = 2000000; /* in microseconds */ + + private static native int halOpen(); + private static native int halTransmit(int halObject, int carrierFrequency, int[] pattern); + private static native int[] halGetCarrierFrequencies(int halObject); + + private final Context mContext; + private final PowerManager.WakeLock mWakeLock; + private final int mHal; + private final Object mHalLock = new Object(); + + ConsumerIrService(Context context) { + mContext = context; + PowerManager pm = (PowerManager)context.getSystemService( + Context.POWER_SERVICE); + mWakeLock = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); + mWakeLock.setReferenceCounted(true); + + mHal = halOpen(); + if (mContext.getPackageManager().hasSystemFeature(PackageManager.FEATURE_CONSUMER_IR)) { + if (mHal == 0) { + throw new RuntimeException("FEATURE_CONSUMER_IR present, but no IR HAL loaded!"); + } + } else if (mHal != 0) { + throw new RuntimeException("IR HAL present, but FEATURE_CONSUMER_IR is not set!"); + } + } + + @Override + public boolean hasIrEmitter() { + return mHal != 0; + } + + private void throwIfNoIrEmitter() { + if (mHal == 0) { + throw new UnsupportedOperationException("IR emitter not available"); + } + } + + + @Override + public void transmit(String packageName, int carrierFrequency, int[] pattern) { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.TRANSMIT_IR) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires TRANSMIT_IR permission"); + } + + long totalXmitTime = 0; + + for (int slice : pattern) { + if (slice <= 0) { + throw new IllegalArgumentException("Non-positive IR slice"); + } + totalXmitTime += slice; + } + + if (totalXmitTime > MAX_XMIT_TIME ) { + throw new IllegalArgumentException("IR pattern too long"); + } + + throwIfNoIrEmitter(); + + // Right now there is no mechanism to ensure fair queing of IR requests + synchronized (mHalLock) { + int err = halTransmit(mHal, carrierFrequency, pattern); + + if (err < 0) { + Slog.e(TAG, "Error transmitting: " + err); + } + } + } + + @Override + public int[] getCarrierFrequencies() { + if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.TRANSMIT_IR) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires TRANSMIT_IR permission"); + } + + throwIfNoIrEmitter(); + + synchronized(mHalLock) { + return halGetCarrierFrequencies(mHal); + } + } +} diff --git a/services/java/com/android/server/CountryDetectorService.java b/services/java/com/android/server/CountryDetectorService.java index fc762777727a..a478b2f47637 100644 --- a/services/java/com/android/server/CountryDetectorService.java +++ b/services/java/com/android/server/CountryDetectorService.java @@ -20,6 +20,7 @@ import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.HashMap; +import com.android.internal.os.BackgroundThread; import com.android.server.location.ComprehensiveCountryDetector; import android.content.Context; @@ -29,8 +30,6 @@ import android.location.ICountryDetector; import android.location.ICountryListener; import android.os.Handler; import android.os.IBinder; -import android.os.Looper; -import android.os.Process; import android.os.RemoteException; import android.util.PrintWriterPrinter; import android.util.Printer; @@ -98,11 +97,12 @@ public class CountryDetectorService extends ICountryDetector.Stub implements Run } @Override - public Country detectCountry() throws RemoteException { + public Country detectCountry() { if (!mSystemReady) { - throw new RemoteException(); + return null; // server not yet active + } else { + return mCountryDetector.detectCountry(); } - return mCountryDetector.detectCountry(); } /** @@ -167,10 +167,9 @@ public class CountryDetectorService extends ICountryDetector.Stub implements Run } } - void systemReady() { + void systemRunning() { // Shall we wait for the initialization finish. - Thread thread = new Thread(this, "CountryDetectorService"); - thread.start(); + BackgroundThread.getHandler().post(this); } private void initialize() { @@ -187,12 +186,9 @@ public class CountryDetectorService extends ICountryDetector.Stub implements Run } public void run() { - Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); - Looper.prepare(); mHandler = new Handler(); initialize(); mSystemReady = true; - Looper.loop(); } protected void setCountryListener(final CountryListener listener) { diff --git a/services/java/com/android/server/DevicePolicyManagerService.java b/services/java/com/android/server/DevicePolicyManagerService.java index dfdde0ca6231..b0fe6a76e945 100644 --- a/services/java/com/android/server/DevicePolicyManagerService.java +++ b/services/java/com/android/server/DevicePolicyManagerService.java @@ -16,11 +16,15 @@ package com.android.server; +import static android.Manifest.permission.MANAGE_CA_CERTIFICATES; + +import com.android.internal.R; import com.android.internal.os.storage.ExternalStorageFormatter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.JournaledFile; import com.android.internal.util.XmlUtils; import com.android.internal.widget.LockPatternUtils; +import com.android.org.conscrypt.TrustedCertificateStore; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -30,6 +34,9 @@ import android.app.Activity; import android.app.ActivityManagerNative; import android.app.AlarmManager; import android.app.AppGlobals; +import android.app.INotificationManager; +import android.app.Notification; +import android.app.NotificationManager; import android.app.PendingIntent; import android.app.admin.DeviceAdminInfo; import android.app.admin.DeviceAdminReceiver; @@ -49,7 +56,9 @@ import android.content.pm.Signature; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.ResolveInfo; import android.net.ProxyProperties; +import android.content.pm.UserInfo; import android.net.Uri; +import android.os.AsyncTask; import android.os.Binder; import android.os.Bundle; import android.os.Environment; @@ -67,7 +76,12 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.UserManager; import android.provider.Settings; +import android.security.Credentials; +import android.security.IKeyChainService; +import android.security.KeyChain; +import android.security.KeyChain.KeyChainConnection; import android.util.AtomicFile; +import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Printer; import android.util.Slog; @@ -76,6 +90,7 @@ import android.util.Xml; import android.view.IWindowManager; import android.view.WindowManagerPolicy; +import java.io.ByteArrayInputStream; import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; @@ -83,8 +98,15 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; +import java.security.KeyStore.TrustedCertificateEntry; +import java.security.cert.CertificateException; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; import java.text.DateFormat; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.HashMap; import java.util.List; @@ -108,6 +130,8 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { protected static final String ACTION_EXPIRED_PASSWORD_NOTIFICATION = "com.android.server.ACTION_EXPIRED_PASSWORD_NOTIFICATION"; + private static final int MONITORING_CERT_NOTIFICATION_ID = R.string.ssl_ca_cert_warning; + private static final boolean DBG = false; final Context mContext; @@ -115,9 +139,16 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { IPowerManager mIPowerManager; IWindowManager mIWindowManager; + NotificationManager mNotificationManager; private DeviceOwner mDeviceOwner; + /** + * Whether or not device admin feature is supported. If it isn't return defaults for all + * public methods. + */ + private boolean mHasFeature; + public static class DevicePolicyData { int mActivePasswordQuality = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; int mActivePasswordLength = 0; @@ -162,7 +193,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { handlePasswordExpirationNotification(getUserData(userHandle)); } }); - } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + } + if (Intent.ACTION_BOOT_COMPLETED.equals(action) + || KeyChain.ACTION_STORAGE_CHANGED.equals(action)) { + manageMonitoringCertificateNotification(intent); + } + if (Intent.ACTION_USER_REMOVED.equals(action)) { removeUserData(userHandle); } else if (Intent.ACTION_USER_STARTED.equals(action) || Intent.ACTION_PACKAGE_CHANGED.equals(action) @@ -504,13 +540,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { */ public DevicePolicyManagerService(Context context) { mContext = context; + mHasFeature = context.getPackageManager().hasSystemFeature( + PackageManager.FEATURE_DEVICE_ADMIN); mWakeLock = ((PowerManager)context.getSystemService(Context.POWER_SERVICE)) .newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "DPM"); + if (!mHasFeature) { + // Skip the rest of the initialization + return; + } IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_BOOT_COMPLETED); filter.addAction(ACTION_EXPIRED_PASSWORD_NOTIFICATION); filter.addAction(Intent.ACTION_USER_REMOVED); filter.addAction(Intent.ACTION_USER_STARTED); + filter.addAction(KeyChain.ACTION_STORAGE_CHANGED); context.registerReceiverAsUser(mReceiver, UserHandle.ALL, filter, null, mHandler); filter = new IntentFilter(); filter.addAction(Intent.ACTION_PACKAGE_CHANGED); @@ -620,6 +663,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mIWindowManager; } + private NotificationManager getNotificationManager() { + if (mNotificationManager == null) { + mNotificationManager = + (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); + } + return mNotificationManager; + } + ActiveAdmin getActiveAdminUncheckedLocked(ComponentName who, int userHandle) { ActiveAdmin admin = getUserData(userHandle).mAdminMap.get(who); if (admin != null @@ -723,6 +774,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public DeviceAdminInfo findAdmin(ComponentName adminName, int userHandle) { + if (!mHasFeature) { + return null; + } enforceCrossUserPermission(userHandle); Intent resolveIntent = new Intent(); resolveIntent.setComponent(adminName); @@ -1012,6 +1066,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void systemReady() { + if (!mHasFeature) { + return; + } synchronized (this) { loadSettingsLocked(getUserData(UserHandle.USER_OWNER), UserHandle.USER_OWNER); loadDeviceOwner(); @@ -1038,13 +1095,73 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + private void manageMonitoringCertificateNotification(Intent intent) { + final NotificationManager notificationManager = getNotificationManager(); + + final boolean hasCert = DevicePolicyManager.hasAnyCaCertsInstalled(); + if (! hasCert) { + if (intent.getAction().equals(KeyChain.ACTION_STORAGE_CHANGED)) { + UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + for (UserInfo user : um.getUsers()) { + notificationManager.cancelAsUser( + null, MONITORING_CERT_NOTIFICATION_ID, user.getUserHandle()); + } + } + return; + } + final boolean isManaged = getDeviceOwner() != null; + int smallIconId; + String contentText; + if (isManaged) { + contentText = mContext.getString(R.string.ssl_ca_cert_noti_managed, + getDeviceOwnerName()); + smallIconId = R.drawable.stat_sys_certificate_info; + } else { + contentText = mContext.getString(R.string.ssl_ca_cert_noti_by_unknown); + smallIconId = android.R.drawable.stat_sys_warning; + } + + Intent dialogIntent = new Intent(Settings.ACTION_MONITORING_CERT_INFO); + dialogIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); + dialogIntent.setPackage("com.android.settings"); + // Notification will be sent individually to all users. The activity should start as + // whichever user is current when it starts. + PendingIntent notifyIntent = PendingIntent.getActivityAsUser(mContext, 0, dialogIntent, + PendingIntent.FLAG_UPDATE_CURRENT, null, UserHandle.CURRENT); + + Notification noti = new Notification.Builder(mContext) + .setSmallIcon(smallIconId) + .setContentTitle(mContext.getString(R.string.ssl_ca_cert_warning)) + .setContentText(contentText) + .setContentIntent(notifyIntent) + .setPriority(Notification.PRIORITY_HIGH) + .setShowWhen(false) + .build(); + + // If this is a boot intent, this will fire for each user. But if this is a storage changed + // intent, it will fire once, so we need to notify all users. + if (intent.getAction().equals(KeyChain.ACTION_STORAGE_CHANGED)) { + UserManager um = (UserManager) mContext.getSystemService(Context.USER_SERVICE); + for (UserInfo user : um.getUsers()) { + notificationManager.notifyAsUser( + null, MONITORING_CERT_NOTIFICATION_ID, noti, user.getUserHandle()); + } + } else { + notificationManager.notifyAsUser( + null, MONITORING_CERT_NOTIFICATION_ID, noti, UserHandle.CURRENT); + } + } + /** * @param adminReceiver The admin to add * @param refreshing true = update an active admin, no error */ public void setActiveAdmin(ComponentName adminReceiver, boolean refreshing, int userHandle) { + if (!mHasFeature) { + return; + } mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.BIND_DEVICE_ADMIN, null); + android.Manifest.permission.MANAGE_DEVICE_ADMINS, null); enforceCrossUserPermission(userHandle); DevicePolicyData policy = getUserData(userHandle); @@ -1086,6 +1203,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public boolean isAdminActive(ComponentName adminReceiver, int userHandle) { + if (!mHasFeature) { + return false; + } enforceCrossUserPermission(userHandle); synchronized (this) { return getActiveAdminUncheckedLocked(adminReceiver, userHandle) != null; @@ -1093,6 +1213,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public boolean hasGrantedPolicy(ComponentName adminReceiver, int policyId, int userHandle) { + if (!mHasFeature) { + return false; + } enforceCrossUserPermission(userHandle); synchronized (this) { ActiveAdmin administrator = getActiveAdminUncheckedLocked(adminReceiver, userHandle); @@ -1103,7 +1226,12 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } } + @SuppressWarnings("unchecked") public List<ComponentName> getActiveAdmins(int userHandle) { + if (!mHasFeature) { + return Collections.EMPTY_LIST; + } + enforceCrossUserPermission(userHandle); synchronized (this) { DevicePolicyData policy = getUserData(userHandle); @@ -1120,6 +1248,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public boolean packageHasActiveAdmins(String packageName, int userHandle) { + if (!mHasFeature) { + return false; + } enforceCrossUserPermission(userHandle); synchronized (this) { DevicePolicyData policy = getUserData(userHandle); @@ -1134,6 +1265,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void removeActiveAdmin(ComponentName adminReceiver, int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); synchronized (this) { ActiveAdmin admin = getActiveAdminUncheckedLocked(adminReceiver, userHandle); @@ -1147,7 +1281,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return; } mContext.enforceCallingOrSelfPermission( - android.Manifest.permission.BIND_DEVICE_ADMIN, null); + android.Manifest.permission.MANAGE_DEVICE_ADMINS, null); } long ident = Binder.clearCallingIdentity(); try { @@ -1159,6 +1293,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void setPasswordQuality(ComponentName who, int quality, int userHandle) { + if (!mHasFeature) { + return; + } validateQualityConstant(quality); enforceCrossUserPermission(userHandle); @@ -1176,6 +1313,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public int getPasswordQuality(ComponentName who, int userHandle) { + if (!mHasFeature) { + return DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; + } enforceCrossUserPermission(userHandle); synchronized (this) { int mode = DevicePolicyManager.PASSWORD_QUALITY_UNSPECIFIED; @@ -1198,6 +1338,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void setPasswordMinimumLength(ComponentName who, int length, int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); synchronized (this) { if (who == null) { @@ -1213,6 +1356,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public int getPasswordMinimumLength(ComponentName who, int userHandle) { + if (!mHasFeature) { + return 0; + } enforceCrossUserPermission(userHandle); synchronized (this) { DevicePolicyData policy = getUserData(userHandle); @@ -1235,6 +1381,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void setPasswordHistoryLength(ComponentName who, int length, int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); synchronized (this) { if (who == null) { @@ -1250,6 +1399,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public int getPasswordHistoryLength(ComponentName who, int userHandle) { + if (!mHasFeature) { + return 0; + } enforceCrossUserPermission(userHandle); synchronized (this) { DevicePolicyData policy = getUserData(userHandle); @@ -1272,6 +1424,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void setPasswordExpirationTimeout(ComponentName who, long timeout, int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); synchronized (this) { if (who == null) { @@ -1302,6 +1457,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * Returns 0 if not configured. */ public long getPasswordExpirationTimeout(ComponentName who, int userHandle) { + if (!mHasFeature) { + return 0L; + } enforceCrossUserPermission(userHandle); synchronized (this) { if (who != null) { @@ -1347,6 +1505,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public long getPasswordExpiration(ComponentName who, int userHandle) { + if (!mHasFeature) { + return 0L; + } enforceCrossUserPermission(userHandle); synchronized (this) { return getPasswordExpirationLocked(who, userHandle); @@ -1354,6 +1515,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void setPasswordMinimumUpperCase(ComponentName who, int length, int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); synchronized (this) { if (who == null) { @@ -1369,6 +1533,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public int getPasswordMinimumUpperCase(ComponentName who, int userHandle) { + if (!mHasFeature) { + return 0; + } enforceCrossUserPermission(userHandle); synchronized (this) { int length = 0; @@ -1406,6 +1573,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public int getPasswordMinimumLowerCase(ComponentName who, int userHandle) { + if (!mHasFeature) { + return 0; + } enforceCrossUserPermission(userHandle); synchronized (this) { int length = 0; @@ -1428,6 +1598,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void setPasswordMinimumLetters(ComponentName who, int length, int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); synchronized (this) { if (who == null) { @@ -1443,6 +1616,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public int getPasswordMinimumLetters(ComponentName who, int userHandle) { + if (!mHasFeature) { + return 0; + } enforceCrossUserPermission(userHandle); synchronized (this) { int length = 0; @@ -1465,6 +1641,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void setPasswordMinimumNumeric(ComponentName who, int length, int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); synchronized (this) { if (who == null) { @@ -1480,6 +1659,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public int getPasswordMinimumNumeric(ComponentName who, int userHandle) { + if (!mHasFeature) { + return 0; + } enforceCrossUserPermission(userHandle); synchronized (this) { int length = 0; @@ -1502,6 +1684,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void setPasswordMinimumSymbols(ComponentName who, int length, int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); synchronized (this) { if (who == null) { @@ -1517,6 +1702,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public int getPasswordMinimumSymbols(ComponentName who, int userHandle) { + if (!mHasFeature) { + return 0; + } enforceCrossUserPermission(userHandle); synchronized (this) { int length = 0; @@ -1539,6 +1727,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void setPasswordMinimumNonLetter(ComponentName who, int length, int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); synchronized (this) { if (who == null) { @@ -1554,6 +1745,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public int getPasswordMinimumNonLetter(ComponentName who, int userHandle) { + if (!mHasFeature) { + return 0; + } enforceCrossUserPermission(userHandle); synchronized (this) { int length = 0; @@ -1576,6 +1770,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public boolean isActivePasswordSufficient(int userHandle) { + if (!mHasFeature) { + return true; + } enforceCrossUserPermission(userHandle); synchronized (this) { DevicePolicyData policy = getUserData(userHandle); @@ -1611,6 +1808,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void setMaximumFailedPasswordsForWipe(ComponentName who, int num, int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); synchronized (this) { // This API can only be called by an active device admin, @@ -1627,6 +1827,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public int getMaximumFailedPasswordsForWipe(ComponentName who, int userHandle) { + if (!mHasFeature) { + return 0; + } enforceCrossUserPermission(userHandle); synchronized (this) { DevicePolicyData policy = getUserData(userHandle); @@ -1652,6 +1855,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public boolean resetPassword(String password, int flags, int userHandle) { + if (!mHasFeature) { + return false; + } enforceCrossUserPermission(userHandle); int quality; synchronized (this) { @@ -1773,6 +1979,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void setMaximumTimeToLock(ComponentName who, long timeMs, int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); synchronized (this) { if (who == null) { @@ -1818,6 +2027,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public long getMaximumTimeToLock(ComponentName who, int userHandle) { + if (!mHasFeature) { + return 0; + } enforceCrossUserPermission(userHandle); synchronized (this) { long time = 0; @@ -1843,6 +2055,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void lockNow() { + if (!mHasFeature) { + return; + } synchronized (this) { // This API can only be called by an active device admin, // so try to retrieve it to check that the caller is one. @@ -1871,6 +2086,76 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return !"".equals(state); } + public boolean installCaCert(byte[] certBuffer) throws RemoteException { + mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null); + KeyChainConnection keyChainConnection = null; + byte[] pemCert; + try { + X509Certificate cert = parseCert(certBuffer); + pemCert = Credentials.convertToPem(cert); + } catch (CertificateException ce) { + Log.e(TAG, "Problem converting cert", ce); + return false; + } catch (IOException ioe) { + Log.e(TAG, "Problem reading cert", ioe); + return false; + } + try { + keyChainConnection = KeyChain.bind(mContext); + try { + keyChainConnection.getService().installCaCertificate(pemCert); + return true; + } finally { + if (keyChainConnection != null) { + keyChainConnection.close(); + keyChainConnection = null; + } + } + } catch (InterruptedException e1) { + Log.w(TAG, "installCaCertsToKeyChain(): ", e1); + Thread.currentThread().interrupt(); + } + return false; + } + + private static X509Certificate parseCert(byte[] certBuffer) + throws CertificateException, IOException { + CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); + return (X509Certificate) certFactory.generateCertificate(new ByteArrayInputStream( + certBuffer)); + } + + public void uninstallCaCert(final byte[] certBuffer) { + mContext.enforceCallingOrSelfPermission(MANAGE_CA_CERTIFICATES, null); + TrustedCertificateStore certStore = new TrustedCertificateStore(); + String alias = null; + try { + X509Certificate cert = parseCert(certBuffer); + alias = certStore.getCertificateAlias(cert); + } catch (CertificateException ce) { + Log.e(TAG, "Problem creating X509Certificate", ce); + return; + } catch (IOException ioe) { + Log.e(TAG, "Problem reading certificate", ioe); + return; + } + try { + KeyChainConnection keyChainConnection = KeyChain.bind(mContext); + IKeyChainService service = keyChainConnection.getService(); + try { + service.deleteCaCertificate(alias); + } catch (RemoteException e) { + Log.e(TAG, "from CaCertUninstaller: ", e); + } finally { + keyChainConnection.close(); + keyChainConnection = null; + } + } catch (InterruptedException ie) { + Log.w(TAG, "CaCertUninstaller: ", ie); + Thread.currentThread().interrupt(); + } + } + void wipeDataLocked(int flags) { // If the SD card is encrypted and non-removable, we have to force a wipe. boolean forceExtWipe = !Environment.isExternalStorageRemovable() && isExtStorageEncrypted(); @@ -1893,6 +2178,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void wipeData(int flags, final int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); synchronized (this) { // This API can only be called by an active device admin, @@ -1928,6 +2216,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public void getRemoveWarning(ComponentName comp, final RemoteCallback result, int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); @@ -1958,6 +2249,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public void setActivePasswordState(int quality, int length, int letters, int uppercase, int lowercase, int numbers, int symbols, int nonletter, int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); mContext.enforceCallingOrSelfPermission( android.Manifest.permission.BIND_DEVICE_ADMIN, null); @@ -2024,12 +2318,14 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { try { policy.mFailedPasswordAttempts++; saveSettingsLocked(userHandle); - int max = getMaximumFailedPasswordsForWipe(null, userHandle); - if (max > 0 && policy.mFailedPasswordAttempts >= max) { - wipeDeviceOrUserLocked(0, userHandle); + if (mHasFeature) { + int max = getMaximumFailedPasswordsForWipe(null, userHandle); + if (max > 0 && policy.mFailedPasswordAttempts >= max) { + wipeDeviceOrUserLocked(0, userHandle); + } + sendAdminCommandLocked(DeviceAdminReceiver.ACTION_PASSWORD_FAILED, + DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, userHandle); } - sendAdminCommandLocked(DeviceAdminReceiver.ACTION_PASSWORD_FAILED, - DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, userHandle); } finally { Binder.restoreCallingIdentity(ident); } @@ -2049,8 +2345,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { policy.mFailedPasswordAttempts = 0; policy.mPasswordOwner = -1; saveSettingsLocked(userHandle); - sendAdminCommandLocked(DeviceAdminReceiver.ACTION_PASSWORD_SUCCEEDED, - DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, userHandle); + if (mHasFeature) { + sendAdminCommandLocked(DeviceAdminReceiver.ACTION_PASSWORD_SUCCEEDED, + DeviceAdminInfo.USES_POLICY_WATCH_LOGIN, userHandle); + } } finally { Binder.restoreCallingIdentity(ident); } @@ -2060,6 +2358,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { public ComponentName setGlobalProxy(ComponentName who, String proxySpec, String exclusionList, int userHandle) { + if (!mHasFeature) { + return null; + } enforceCrossUserPermission(userHandle); synchronized(this) { if (who == null) { @@ -2110,6 +2411,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } public ComponentName getGlobalProxyAdmin(int userHandle) { + if (!mHasFeature) { + return null; + } enforceCrossUserPermission(userHandle); synchronized(this) { DevicePolicyData policy = getUserData(UserHandle.USER_OWNER); @@ -2177,6 +2481,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * status (for all admins). */ public int setStorageEncryption(ComponentName who, boolean encrypt, int userHandle) { + if (!mHasFeature) { + return DevicePolicyManager.ENCRYPTION_STATUS_UNSUPPORTED; + } enforceCrossUserPermission(userHandle); synchronized (this) { // Check for permissions @@ -2228,6 +2535,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * active admins. */ public boolean getStorageEncryption(ComponentName who, int userHandle) { + if (!mHasFeature) { + return false; + } enforceCrossUserPermission(userHandle); synchronized (this) { // Check for permissions if a particular caller is specified @@ -2254,6 +2564,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * Get the current encryption status of the device. */ public int getStorageEncryptionStatus(int userHandle) { + if (!mHasFeature) { + // Ok to return current status. + } enforceCrossUserPermission(userHandle); return getEncryptionStatus(); } @@ -2302,6 +2615,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * Disables all device cameras according to the specified admin. */ public void setCameraDisabled(ComponentName who, boolean disabled, int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); synchronized (this) { if (who == null) { @@ -2322,6 +2638,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * active admins. */ public boolean getCameraDisabled(ComponentName who, int userHandle) { + if (!mHasFeature) { + return false; + } synchronized (this) { if (who != null) { ActiveAdmin admin = getActiveAdminUncheckedLocked(who, userHandle); @@ -2345,6 +2664,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * Selectively disable keyguard features. */ public void setKeyguardDisabledFeatures(ComponentName who, int which, int userHandle) { + if (!mHasFeature) { + return; + } enforceCrossUserPermission(userHandle); synchronized (this) { if (who == null) { @@ -2365,6 +2687,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { * or the aggregate of all active admins if who is null. */ public int getKeyguardDisabledFeatures(ComponentName who, int userHandle) { + if (!mHasFeature) { + return 0; + } enforceCrossUserPermission(userHandle); synchronized (this) { if (who != null) { @@ -2385,7 +2710,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } @Override - public boolean setDeviceOwner(String packageName) { + public boolean setDeviceOwner(String packageName, String ownerName) { + if (!mHasFeature) { + return false; + } if (packageName == null || !DeviceOwner.isInstalled(packageName, mContext.getPackageManager())) { throw new IllegalArgumentException("Invalid package name " + packageName @@ -2393,7 +2721,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { } synchronized (this) { if (mDeviceOwner == null && !isDeviceProvisioned()) { - mDeviceOwner = new DeviceOwner(packageName); + mDeviceOwner = new DeviceOwner(packageName, ownerName); mDeviceOwner.writeOwnerFile(); return true; } else { @@ -2406,6 +2734,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public boolean isDeviceOwner(String packageName) { + if (!mHasFeature) { + return false; + } synchronized (this) { return mDeviceOwner != null && mDeviceOwner.getPackageName().equals(packageName); @@ -2414,6 +2745,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { @Override public String getDeviceOwner() { + if (!mHasFeature) { + return null; + } synchronized (this) { if (mDeviceOwner != null) { return mDeviceOwner.getPackageName(); @@ -2422,6 +2756,20 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return null; } + @Override + public String getDeviceOwnerName() { + if (!mHasFeature) { + return null; + } + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null); + synchronized (this) { + if (mDeviceOwner != null) { + return mDeviceOwner.getName(); + } + } + return null; + } + private boolean isDeviceProvisioned() { return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.DEVICE_PROVISIONED, 0) > 0; @@ -2495,15 +2843,18 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { static class DeviceOwner { private static final String DEVICE_OWNER_XML = "device_owner.xml"; private static final String TAG_DEVICE_OWNER = "device-owner"; + private static final String ATTR_NAME = "name"; private static final String ATTR_PACKAGE = "package"; private String mPackageName; + private String mOwnerName; DeviceOwner() { readOwnerFile(); } - DeviceOwner(String packageName) { + DeviceOwner(String packageName, String ownerName) { this.mPackageName = packageName; + this.mOwnerName = ownerName; } static boolean isRegistered() { @@ -2515,6 +2866,10 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { return mPackageName; } + String getName() { + return mOwnerName; + } + static boolean isInstalled(String packageName, PackageManager pm) { try { PackageInfo pi; @@ -2546,6 +2901,7 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { "Device Owner file does not start with device-owner tag: found " + tag); } mPackageName = parser.getAttributeValue(null, ATTR_PACKAGE); + mOwnerName = parser.getAttributeValue(null, ATTR_NAME); input.close(); } catch (XmlPullParserException xppe) { Slog.e(TAG, "Error parsing device-owner file\n" + xppe); @@ -2570,6 +2926,9 @@ public class DevicePolicyManagerService extends IDevicePolicyManager.Stub { out.startDocument(null, true); out.startTag(null, TAG_DEVICE_OWNER); out.attribute(null, ATTR_PACKAGE, mPackageName); + if (mOwnerName != null) { + out.attribute(null, ATTR_NAME, mOwnerName); + } out.endTag(null, TAG_DEVICE_OWNER); out.endDocument(); out.flush(); diff --git a/services/java/com/android/server/DropBoxManagerService.java b/services/java/com/android/server/DropBoxManagerService.java index 5008270db770..29b04da9c564 100644 --- a/services/java/com/android/server/DropBoxManagerService.java +++ b/services/java/com/android/server/DropBoxManagerService.java @@ -24,6 +24,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.net.Uri; +import android.os.Binder; import android.os.Debug; import android.os.DropBoxManager; import android.os.FileUtils; @@ -265,8 +266,13 @@ public final class DropBoxManagerService extends IDropBoxManagerService.Stub { } public boolean isTagEnabled(String tag) { - return !"disabled".equals(Settings.Global.getString( - mContentResolver, Settings.Global.DROPBOX_TAG_PREFIX + tag)); + final long token = Binder.clearCallingIdentity(); + try { + return !"disabled".equals(Settings.Global.getString( + mContentResolver, Settings.Global.DROPBOX_TAG_PREFIX + tag)); + } finally { + Binder.restoreCallingIdentity(token); + } } public synchronized DropBoxManager.Entry getNextEntry(String tag, long millis) { diff --git a/services/java/com/android/server/FgThread.java b/services/java/com/android/server/FgThread.java new file mode 100644 index 000000000000..3b655f2cee93 --- /dev/null +++ b/services/java/com/android/server/FgThread.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.os.Handler; +import android.os.HandlerThread; + +/** + * Shared singleton foreground thread for the system. This is a thread for regular + * foreground service operations, which shouldn't be blocked by anything running in + * the background. In particular, the shared background thread could be doing + * relatively long-running operations like saving state to disk (in addition to + * simply being a background priority), which can cause operations scheduled on it + * to be delayed for a user-noticeable amount of time. + */ +public final class FgThread extends HandlerThread { + private static FgThread sInstance; + private static Handler sHandler; + + private FgThread() { + super("android.fg", android.os.Process.THREAD_PRIORITY_DEFAULT); + } + + private static void ensureThreadLocked() { + if (sInstance == null) { + sInstance = new FgThread(); + sInstance.start(); + sHandler = new Handler(sInstance.getLooper()); + sHandler.post(new Runnable() { + @Override + public void run() { + android.os.Process.setCanSelfBackground(false); + } + }); + } + } + + public static FgThread get() { + synchronized (UiThread.class) { + ensureThreadLocked(); + return sInstance; + } + } + + public static Handler getHandler() { + synchronized (UiThread.class) { + ensureThreadLocked(); + return sHandler; + } + } +} diff --git a/services/java/com/android/server/IdleMaintenanceService.java b/services/java/com/android/server/IdleMaintenanceService.java index 584d4bc5f4c8..b0a1aca37d7c 100644 --- a/services/java/com/android/server/IdleMaintenanceService.java +++ b/services/java/com/android/server/IdleMaintenanceService.java @@ -17,6 +17,7 @@ package com.android.server; import android.app.Activity; +import android.app.ActivityManagerNative; import android.app.AlarmManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -24,12 +25,13 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.os.Handler; -import android.os.Looper; import android.os.PowerManager; import android.os.PowerManager.WakeLock; +import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.util.Log; +import android.util.Slog; /** * This service observes the device state and when applicable sends @@ -69,6 +71,9 @@ public class IdleMaintenanceService extends BroadcastReceiver { private static final String ACTION_UPDATE_IDLE_MAINTENANCE_STATE = "com.android.server.IdleMaintenanceService.action.UPDATE_IDLE_MAINTENANCE_STATE"; + private static final String ACTION_FORCE_IDLE_MAINTENANCE = + "com.android.server.IdleMaintenanceService.action.FORCE_IDLE_MAINTENANCE"; + private static final Intent sIdleMaintenanceStartIntent; static { sIdleMaintenanceStartIntent = new Intent(Intent.ACTION_IDLE_MAINTENANCE_START); @@ -115,10 +120,10 @@ public class IdleMaintenanceService extends BroadcastReceiver { mUpdateIdleMaintenanceStatePendingIntent = PendingIntent.getBroadcast(mContext, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); - register(mContext.getMainLooper()); + register(mHandler); } - public void register(Looper looper) { + public void register(Handler handler) { IntentFilter intentFilter = new IntentFilter(); // Alarm actions. @@ -136,7 +141,12 @@ public class IdleMaintenanceService extends BroadcastReceiver { intentFilter.addAction(Intent.ACTION_DREAMING_STOPPED); mContext.registerReceiverAsUser(this, UserHandle.ALL, - intentFilter, null, new Handler(looper)); + intentFilter, null, mHandler); + + intentFilter = new IntentFilter(); + intentFilter.addAction(ACTION_FORCE_IDLE_MAINTENANCE); + mContext.registerReceiverAsUser(this, UserHandle.ALL, + intentFilter, android.Manifest.permission.SET_ACTIVITY_WATCHER, mHandler); } private void scheduleUpdateIdleMaintenanceState(long delayMillis) { @@ -149,7 +159,7 @@ public class IdleMaintenanceService extends BroadcastReceiver { mAlarmService.cancel(mUpdateIdleMaintenanceStatePendingIntent); } - private void updateIdleMaintenanceState() { + private void updateIdleMaintenanceState(boolean noisy) { if (mIdleMaintenanceStarted) { // Idle maintenance can be interrupted by user activity, or duration // time out, or low battery. @@ -170,9 +180,9 @@ public class IdleMaintenanceService extends BroadcastReceiver { getNextIdleMaintenanceIntervalStartFromNow()); } } - } else if (deviceStatePermitsIdleMaintenanceStart() - && lastUserActivityPermitsIdleMaintenanceStart() - && lastRunPermitsIdleMaintenanceStart()) { + } else if (deviceStatePermitsIdleMaintenanceStart(noisy) + && lastUserActivityPermitsIdleMaintenanceStart(noisy) + && lastRunPermitsIdleMaintenanceStart(noisy)) { // Now that we started idle maintenance, we should schedule another // update for the moment when the idle maintenance times out. scheduleUpdateIdleMaintenanceState(MAX_IDLE_MAINTENANCE_DURATION); @@ -182,8 +192,8 @@ public class IdleMaintenanceService extends BroadcastReceiver { isBatteryCharging() ? 1 : 0); mLastIdleMaintenanceStartTimeMillis = SystemClock.elapsedRealtime(); sendIdleMaintenanceStartIntent(); - } else if (lastUserActivityPermitsIdleMaintenanceStart()) { - if (lastRunPermitsIdleMaintenanceStart()) { + } else if (lastUserActivityPermitsIdleMaintenanceStart(noisy)) { + if (lastRunPermitsIdleMaintenanceStart(noisy)) { // The user does not use the device and we did not run maintenance in more // than the min interval between runs, so schedule an update - maybe the // battery will be charged latter. @@ -204,6 +214,10 @@ public class IdleMaintenanceService extends BroadcastReceiver { private void sendIdleMaintenanceStartIntent() { mWakeLock.acquire(); + try { + ActivityManagerNative.getDefault().performIdleMaintenance(); + } catch (RemoteException e) { + } mContext.sendOrderedBroadcastAsUser(sIdleMaintenanceStartIntent, UserHandle.ALL, null, this, mHandler, Activity.RESULT_OK, null, null); } @@ -214,25 +228,37 @@ public class IdleMaintenanceService extends BroadcastReceiver { null, this, mHandler, Activity.RESULT_OK, null, null); } - private boolean deviceStatePermitsIdleMaintenanceStart() { + private boolean deviceStatePermitsIdleMaintenanceStart(boolean noisy) { final int minBatteryLevel = isBatteryCharging() ? MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_CHARGING : MIN_BATTERY_LEVEL_IDLE_MAINTENANCE_START_NOT_CHARGING; - return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID + boolean allowed = (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID && mBatteryService.getBatteryLevel() > minBatteryLevel); + if (!allowed && noisy) { + Slog.i("IdleMaintenance", "Idle maintenance not allowed due to power"); + } + return allowed; } - private boolean lastUserActivityPermitsIdleMaintenanceStart() { + private boolean lastUserActivityPermitsIdleMaintenanceStart(boolean noisy) { // The last time the user poked the device is above the threshold. - return (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID + boolean allowed = (mLastUserActivityElapsedTimeMillis != LAST_USER_ACTIVITY_TIME_INVALID && SystemClock.elapsedRealtime() - mLastUserActivityElapsedTimeMillis > MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START); + if (!allowed && noisy) { + Slog.i("IdleMaintenance", "Idle maintenance not allowed due to last user activity"); + } + return allowed; } - private boolean lastRunPermitsIdleMaintenanceStart() { + private boolean lastRunPermitsIdleMaintenanceStart(boolean noisy) { // Enough time passed since the last maintenance run. - return SystemClock.elapsedRealtime() - mLastIdleMaintenanceStartTimeMillis + boolean allowed = SystemClock.elapsedRealtime() - mLastIdleMaintenanceStartTimeMillis > MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS; + if (!allowed && noisy) { + Slog.i("IdleMaintenance", "Idle maintenance not allowed due time since last"); + } + return allowed; } private boolean lastUserActivityPermitsIdleMaintenanceRunning() { @@ -266,7 +292,7 @@ public class IdleMaintenanceService extends BroadcastReceiver { // next release. The only client for this for now is internal an holds // a wake lock correctly. if (mIdleMaintenanceStarted) { - updateIdleMaintenanceState(); + updateIdleMaintenanceState(false); } } else if (Intent.ACTION_SCREEN_ON.equals(action) || Intent.ACTION_DREAMING_STOPPED.equals(action)) { @@ -276,7 +302,7 @@ public class IdleMaintenanceService extends BroadcastReceiver { unscheduleUpdateIdleMaintenanceState(); // If the screen went on/stopped dreaming, we know the user is using the // device which means that idle maintenance should be stopped if running. - updateIdleMaintenanceState(); + updateIdleMaintenanceState(false); } else if (Intent.ACTION_SCREEN_OFF.equals(action) || Intent.ACTION_DREAMING_STARTED.equals(action)) { mLastUserActivityElapsedTimeMillis = SystemClock.elapsedRealtime(); @@ -285,7 +311,12 @@ public class IdleMaintenanceService extends BroadcastReceiver { // this timeout elapses since the device may go to sleep by then. scheduleUpdateIdleMaintenanceState(MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START); } else if (ACTION_UPDATE_IDLE_MAINTENANCE_STATE.equals(action)) { - updateIdleMaintenanceState(); + updateIdleMaintenanceState(false); + } else if (ACTION_FORCE_IDLE_MAINTENANCE.equals(action)) { + long now = SystemClock.elapsedRealtime() - 1; + mLastUserActivityElapsedTimeMillis = now - MIN_USER_INACTIVITY_IDLE_MAINTENANCE_START; + mLastIdleMaintenanceStartTimeMillis = now - MIN_IDLE_MAINTENANCE_INTERVAL_MILLIS; + updateIdleMaintenanceState(true); } else if (Intent.ACTION_IDLE_MAINTENANCE_START.equals(action) || Intent.ACTION_IDLE_MAINTENANCE_END.equals(action)) { // We were holding a wake lock while broadcasting the idle maintenance diff --git a/services/java/com/android/server/InputMethodManagerService.java b/services/java/com/android/server/InputMethodManagerService.java index c91685710a50..794d274da5fa 100644 --- a/services/java/com/android/server/InputMethodManagerService.java +++ b/services/java/com/android/server/InputMethodManagerService.java @@ -791,6 +791,11 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // InputMethodFileManager should be reset when the user is changed mFileManager = new InputMethodFileManager(mMethodMap, newUserId); final String defaultImiId = mSettings.getSelectedInputMethod(); + // For secondary users, the list of enabled IMEs may not have been updated since the + // callbacks to PackageMonitor are ignored for the secondary user. Here, defaultImiId may + // not be empty even if the IME has been uninstalled by the primary user. + // Even in such cases, IMMS works fine because it will find the most applicable + // IME for that user. final boolean initialUserSwitch = TextUtils.isEmpty(defaultImiId); if (DEBUG) { Slog.d(TAG, "Switch user: " + newUserId + " current ime = " + defaultImiId); @@ -812,13 +817,13 @@ public class InputMethodManagerService extends IInputMethodManager.Stub // The input method manager only throws security exceptions, so let's // log all others. if (!(e instanceof SecurityException)) { - Slog.e(TAG, "Input Method Manager Crash", e); + Slog.wtf(TAG, "Input Method Manager Crash", e); } throw e; } } - public void systemReady(StatusBarManagerService statusBar) { + public void systemRunning(StatusBarManagerService statusBar) { synchronized (mMethodMap) { if (DEBUG) { Slog.d(TAG, "--- systemReady"); @@ -894,7 +899,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub Slog.d(TAG, "--- calledFromForegroundUserOrSystemProcess ? " + "calling uid = " + uid + " system uid = " + Process.SYSTEM_UID + " calling userId = " + userId + ", foreground user id = " - + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid()); + + mSettings.getCurrentUserId() + ", calling pid = " + Binder.getCallingPid() + + InputMethodUtils.getApiCallStack()); } if (uid == Process.SYSTEM_UID || userId == mSettings.getCurrentUserId()) { return true; @@ -914,7 +920,8 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } return true; } - Slog.w(TAG, "--- IPC called from background users. Ignore. \n" + getStackTrace()); + Slog.w(TAG, "--- IPC called from background users. Ignore. \n" + + InputMethodUtils.getStackTrace()); return false; } @@ -962,19 +969,25 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } /** - * @param imi if null, returns enabled subtypes for the current imi + * @param imiId if null, returns enabled subtypes for the current imi * @return enabled subtypes of the specified imi */ @Override - public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(InputMethodInfo imi, + public List<InputMethodSubtype> getEnabledInputMethodSubtypeList(String imiId, boolean allowsImplicitlySelectedSubtypes) { // TODO: Make this work even for non-current users? if (!calledFromValidUser()) { - return Collections.emptyList(); + return Collections.<InputMethodSubtype>emptyList(); } synchronized (mMethodMap) { - if (imi == null && mCurMethodId != null) { + final InputMethodInfo imi; + if (imiId == null && mCurMethodId != null) { imi = mMethodMap.get(mCurMethodId); + } else { + imi = mMethodMap.get(imiId); + } + if (imi == null) { + return Collections.<InputMethodSubtype>emptyList(); } return mSettings.getEnabledInputMethodSubtypeListLocked( mContext, imi, allowsImplicitlySelectedSubtypes); @@ -1205,7 +1218,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub mCurIntent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( mContext, 0, new Intent(Settings.ACTION_INPUT_METHOD_SETTINGS), 0)); if (bindCurrentInputMethodService(mCurIntent, this, Context.BIND_AUTO_CREATE - | Context.BIND_NOT_VISIBLE)) { + | Context.BIND_NOT_VISIBLE | Context.BIND_SHOWING_UI)) { mLastBindTime = SystemClock.uptimeMillis(); mHaveConnection = true; mCurId = info.getId(); @@ -1506,23 +1519,16 @@ public class InputMethodManagerService extends IInputMethodManager.Stub final InputMethodInfo imi = mMethodMap.get(mCurMethodId); if (imi != null && iconVisibility && needsToShowImeSwitchOngoingNotification()) { // Used to load label - final PackageManager pm = mContext.getPackageManager(); final CharSequence title = mRes.getText( com.android.internal.R.string.select_input_method); - final CharSequence imiLabel = imi.loadLabel(pm); - final CharSequence summary = mCurrentSubtype != null - ? TextUtils.concat(mCurrentSubtype.getDisplayName(mContext, - imi.getPackageName(), imi.getServiceInfo().applicationInfo), - (TextUtils.isEmpty(imiLabel) ? - "" : " - " + imiLabel)) - : imiLabel; + final CharSequence summary = InputMethodUtils.getImeAndSubtypeDisplayName( + mContext, imi, mCurrentSubtype); mImeSwitcherNotification.setLatestEventInfo( mContext, title, summary, mImeSwitchPendingIntent); if (mNotificationManager != null) { if (DEBUG) { - Slog.d(TAG, "--- show notification: label = " + imiLabel - + ", summary = " + summary); + Slog.d(TAG, "--- show notification: label = " + summary); } mNotificationManager.notifyAsUser(null, com.android.internal.R.string.select_input_method, @@ -2153,6 +2159,21 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } @Override + public boolean shouldOfferSwitchingToNextInputMethod(IBinder token) { + if (!calledFromValidUser()) { + return false; + } + synchronized (mMethodMap) { + final ImeSubtypeListItem nextSubtype = mImListManager.getNextInputMethod( + false /* onlyCurrentIme */, mMethodMap.get(mCurMethodId), mCurrentSubtype); + if (nextSubtype == null) { + return false; + } + return true; + } + } + + @Override public InputMethodSubtype getLastInputMethodSubtype() { if (!calledFromValidUser()) { return null; @@ -2475,7 +2496,7 @@ public class InputMethodManagerService extends IInputMethodManager.Stub HashMap<String, InputMethodInfo> map, boolean resetDefaultEnabledIme) { if (DEBUG) { Slog.d(TAG, "--- re-buildInputMethodList reset = " + resetDefaultEnabledIme - + " \n ------ \n" + getStackTrace()); + + " \n ------ \n" + InputMethodUtils.getStackTrace()); } list.clear(); map.clear(); @@ -3470,22 +3491,6 @@ public class InputMethodManagerService extends IInputMethodManager.Stub } } - // ---------------------------------------------------------------------- - // Utilities for debug - private static String getStackTrace() { - final StringBuilder sb = new StringBuilder(); - try { - throw new RuntimeException(); - } catch (RuntimeException e) { - final StackTraceElement[] frames = e.getStackTrace(); - // Start at 1 because the first frame is here and we don't care about it - for (int j = 1; j < frames.length; ++j) { - sb.append(frames[j].toString() + "\n"); - } - } - return sb.toString(); - } - @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) diff --git a/services/java/com/android/server/IntentResolver.java b/services/java/com/android/server/IntentResolver.java index 35345f5762ee..64b0487fa320 100644 --- a/services/java/com/android/server/IntentResolver.java +++ b/services/java/com/android/server/IntentResolver.java @@ -18,9 +18,9 @@ package com.android.server; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; @@ -29,15 +29,16 @@ import java.util.Set; import android.net.Uri; import android.util.FastImmutableArraySet; +import android.util.ArrayMap; import android.util.Log; import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.LogPrinter; import android.util.Printer; -import android.util.StringBuilderPrinter; import android.content.Intent; import android.content.IntentFilter; +import com.android.internal.util.FastPrintWriter; /** * {@hide} @@ -46,7 +47,6 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { final private static String TAG = "IntentResolver"; final private static boolean DEBUG = false; final private static boolean localLOGV = DEBUG || false; - final private static boolean VALIDATE = false; public void addFilter(F f) { if (localLOGV) { @@ -67,21 +67,11 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { register_intent_filter(f, f.actionsIterator(), mTypedActionToFilter, " TypedAction: "); } - - if (VALIDATE) { - mOldResolver.addFilter(f); - verifyDataStructures(f); - } } public void removeFilter(F f) { removeFilterInternal(f); mFilters.remove(f); - - if (VALIDATE) { - mOldResolver.removeFilter(f); - verifyDataStructures(f); - } } void removeFilterInternal(F f) { @@ -240,8 +230,8 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); if (debug) Slog.v( - TAG, "Resolving type " + resolvedType + " scheme " + scheme - + " of intent " + intent); + TAG, "Resolving type=" + resolvedType + " scheme=" + scheme + + " defaultOnly=" + defaultOnly + " userId=" + userId + " of " + intent); F[] firstTypeCut = null; F[] secondTypeCut = null; @@ -260,26 +250,28 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { // Not a wild card, so we can just look for all filters that // completely match or wildcards whose base type matches. firstTypeCut = mTypeToFilter.get(resolvedType); - if (debug) Slog.v(TAG, "First type cut: " + firstTypeCut); + if (debug) Slog.v(TAG, "First type cut: " + Arrays.toString(firstTypeCut)); secondTypeCut = mWildTypeToFilter.get(baseType); - if (debug) Slog.v(TAG, "Second type cut: " + secondTypeCut); + if (debug) Slog.v(TAG, "Second type cut: " + + Arrays.toString(secondTypeCut)); } else { // We can match anything with our base type. firstTypeCut = mBaseTypeToFilter.get(baseType); - if (debug) Slog.v(TAG, "First type cut: " + firstTypeCut); + if (debug) Slog.v(TAG, "First type cut: " + Arrays.toString(firstTypeCut)); secondTypeCut = mWildTypeToFilter.get(baseType); - if (debug) Slog.v(TAG, "Second type cut: " + secondTypeCut); + if (debug) Slog.v(TAG, "Second type cut: " + + Arrays.toString(secondTypeCut)); } // Any */* types always apply, but we only need to do this // if the intent type was not already */*. thirdTypeCut = mWildTypeToFilter.get("*"); - if (debug) Slog.v(TAG, "Third type cut: " + thirdTypeCut); + if (debug) Slog.v(TAG, "Third type cut: " + Arrays.toString(thirdTypeCut)); } else if (intent.getAction() != null) { // The intent specified any type ({@literal *}/*). This // can be a whole heck of a lot of things, so as a first // cut let's use the action instead. firstTypeCut = mTypedActionToFilter.get(intent.getAction()); - if (debug) Slog.v(TAG, "Typed Action list: " + firstTypeCut); + if (debug) Slog.v(TAG, "Typed Action list: " + Arrays.toString(firstTypeCut)); } } } @@ -289,7 +281,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { // on the authority and path by directly matching each resulting filter). if (scheme != null) { schemeCut = mSchemeToFilter.get(scheme); - if (debug) Slog.v(TAG, "Scheme list: " + schemeCut); + if (debug) Slog.v(TAG, "Scheme list: " + Arrays.toString(schemeCut)); } // If the intent does not specify any data -- either a MIME type or @@ -297,7 +289,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { // data. if (resolvedType == null && scheme == null && intent.getAction() != null) { firstTypeCut = mActionToFilter.get(intent.getAction()); - if (debug) Slog.v(TAG, "Action list: " + firstTypeCut); + if (debug) Slog.v(TAG, "Action list: " + Arrays.toString(firstTypeCut)); } FastImmutableArraySet<String> categories = getFastIntentCategories(intent); @@ -319,20 +311,10 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } sortResults(finalList); - if (VALIDATE) { - List<R> oldList = mOldResolver.queryIntent(intent, resolvedType, defaultOnly, userId); - if (oldList.size() != finalList.size()) { - ValidationFailure here = new ValidationFailure(); - here.fillInStackTrace(); - Log.wtf(TAG, "Query result " + intent + " size is " + finalList.size() - + "; old implementation is " + oldList.size(), here); - } - } - if (debug) { Slog.v(TAG, "Final result list:"); - for (R r : finalList) { - Slog.v(TAG, " " + r); + for (int i=0; i<finalList.size(); i++) { + Slog.v(TAG, " " + finalList.get(i)); } } return finalList; @@ -379,7 +361,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { out.print(prefix); out.println(filter); } - private final void addFilter(HashMap<String, F[]> map, String name, F filter) { + private final void addFilter(ArrayMap<String, F[]> map, String name, F filter) { F[] array = map.get(name); if (array == null) { array = newArray(2); @@ -464,7 +446,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } private final int register_intent_filter(F filter, Iterator<String> i, - HashMap<String, F[]> dest, String prefix) { + ArrayMap<String, F[]> dest, String prefix) { if (i == null) { return 0; } @@ -480,7 +462,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } private final int unregister_intent_filter(F filter, Iterator<String> i, - HashMap<String, F[]> dest, String prefix) { + ArrayMap<String, F[]> dest, String prefix) { if (i == null) { return 0; } @@ -495,7 +477,7 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { return num; } - private final void remove_all_objects(HashMap<String, F[]> map, String name, + private final void remove_all_objects(ArrayMap<String, F[]> map, String name, Object object) { F[] array = map.get(name); if (array != null) { @@ -540,6 +522,16 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { final boolean excludingStopped = intent.isExcludingStopped(); + final Printer logPrinter; + final PrintWriter logPrintWriter; + if (debug) { + logPrinter = new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM); + logPrintWriter = new FastPrintWriter(logPrinter); + } else { + logPrinter = null; + logPrintWriter = null; + } + final int N = src != null ? src.length : 0; boolean hasNonDefaults = false; int i; @@ -574,11 +566,17 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { match = filter.match(action, resolvedType, scheme, data, categories, TAG); if (match >= 0) { if (debug) Slog.v(TAG, " Filter matched! match=0x" + - Integer.toHexString(match)); + Integer.toHexString(match) + " hasDefault=" + + filter.hasCategory(Intent.CATEGORY_DEFAULT)); if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) { final R oneResult = newResult(filter, match, userId); if (oneResult != null) { dest.add(oneResult); + if (debug) { + dumpFilter(logPrintWriter, " ", filter); + logPrintWriter.flush(); + filter.dump(logPrinter, " "); + } } } else { hasNonDefaults = true; @@ -598,8 +596,12 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } } - if (dest.size() == 0 && hasNonDefaults) { - Slog.w(TAG, "resolveIntent failed: found match, but none with Intent.CATEGORY_DEFAULT"); + if (hasNonDefaults) { + if (dest.size() == 0) { + Slog.w(TAG, "resolveIntent failed: found match, but none with CATEGORY_DEFAULT"); + } else if (dest.size() > 1) { + Slog.w(TAG, "resolveIntent: multiple matches, only some with CATEGORY_DEFAULT"); + } } } @@ -613,120 +615,6 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { } }; - static class ValidationFailure extends RuntimeException { - } - - private void verifyDataStructures(IntentFilter src) { - compareMaps(src, "mTypeToFilter", mTypeToFilter, mOldResolver.mTypeToFilter); - compareMaps(src, "mBaseTypeToFilter", mBaseTypeToFilter, mOldResolver.mBaseTypeToFilter); - compareMaps(src, "mWildTypeToFilter", mWildTypeToFilter, mOldResolver.mWildTypeToFilter); - compareMaps(src, "mSchemeToFilter", mSchemeToFilter, mOldResolver.mSchemeToFilter); - compareMaps(src, "mActionToFilter", mActionToFilter, mOldResolver.mActionToFilter); - compareMaps(src, "mTypedActionToFilter", mTypedActionToFilter, mOldResolver.mTypedActionToFilter); - } - - private void compareMaps(IntentFilter src, String name, HashMap<String, F[]> cur, - HashMap<String, ArrayList<F>> old) { - if (cur.size() != old.size()) { - StringBuilder missing = new StringBuilder(128); - for (Map.Entry<String, ArrayList<F>> e : old.entrySet()) { - final F[] curArray = cur.get(e.getKey()); - if (curArray == null) { - if (missing.length() > 0) { - missing.append(' '); - } - missing.append(e.getKey()); - } - } - StringBuilder extra = new StringBuilder(128); - for (Map.Entry<String, F[]> e : cur.entrySet()) { - if (old.get(e.getKey()) == null) { - if (extra.length() > 0) { - extra.append(' '); - } - extra.append(e.getKey()); - } - } - StringBuilder srcStr = new StringBuilder(1024); - StringBuilderPrinter printer = new StringBuilderPrinter(srcStr); - src.dump(printer, ""); - ValidationFailure here = new ValidationFailure(); - here.fillInStackTrace(); - Log.wtf(TAG, "New map " + name + " size is " + cur.size() - + "; old implementation is " + old.size() - + "; missing: " + missing.toString() - + "; extra: " + extra.toString() - + "; src: " + srcStr.toString(), here); - return; - } - for (Map.Entry<String, ArrayList<F>> e : old.entrySet()) { - final F[] curArray = cur.get(e.getKey()); - int curLen = curArray != null ? curArray.length : 0; - if (curLen == 0) { - ValidationFailure here = new ValidationFailure(); - here.fillInStackTrace(); - Log.wtf(TAG, "New map " + name + " doesn't contain expected key " - + e.getKey() + " (array=" + curArray + ")"); - return; - } - while (curLen > 0 && curArray[curLen-1] == null) { - curLen--; - } - final ArrayList<F> oldArray = e.getValue(); - final int oldLen = oldArray.size(); - if (curLen != oldLen) { - ValidationFailure here = new ValidationFailure(); - here.fillInStackTrace(); - Log.wtf(TAG, "New map " + name + " entry " + e.getKey() + " size is " - + curLen + "; old implementation is " + oldLen, here); - return; - } - for (int i=0; i<oldLen; i++) { - F f = oldArray.get(i); - boolean found = false; - for (int j=0; j<curLen; j++) { - if (curArray[j] == f) { - found = true; - break; - } - } - if (!found) { - ValidationFailure here = new ValidationFailure(); - here.fillInStackTrace(); - Log.wtf(TAG, "New map " + name + " entry + " + e.getKey() - + " doesn't contain expected filter " + f, here); - } - } - for (int i=0; i<curLen; i++) { - if (curArray[i] == null) { - ValidationFailure here = new ValidationFailure(); - here.fillInStackTrace(); - Log.wtf(TAG, "New map " + name + " entry + " + e.getKey() - + " has unexpected null at " + i + "; array: " + curArray, here); - break; - } - } - } - } - - private final IntentResolverOld<F, R> mOldResolver = new IntentResolverOld<F, R>() { - @Override protected boolean isPackageForFilter(String packageName, F filter) { - return IntentResolver.this.isPackageForFilter(packageName, filter); - } - @Override protected boolean allowFilterResult(F filter, List<R> dest) { - return IntentResolver.this.allowFilterResult(filter, dest); - } - @Override protected boolean isFilterStopped(F filter, int userId) { - return IntentResolver.this.isFilterStopped(filter, userId); - } - @Override protected R newResult(F filter, int match, int userId) { - return IntentResolver.this.newResult(filter, match, userId); - } - @Override protected void sortResults(List<R> results) { - IntentResolver.this.sortResults(results); - } - }; - /** * All filters that have been registered. */ @@ -736,14 +624,14 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { * All of the MIME types that have been registered, such as "image/jpeg", * "image/*", or "{@literal *}/*". */ - private final HashMap<String, F[]> mTypeToFilter = new HashMap<String, F[]>(); + private final ArrayMap<String, F[]> mTypeToFilter = new ArrayMap<String, F[]>(); /** * The base names of all of all fully qualified MIME types that have been * registered, such as "image" or "*". Wild card MIME types such as * "image/*" will not be here. */ - private final HashMap<String, F[]> mBaseTypeToFilter = new HashMap<String, F[]>(); + private final ArrayMap<String, F[]> mBaseTypeToFilter = new ArrayMap<String, F[]>(); /** * The base names of all of the MIME types with a sub-type wildcard that @@ -752,21 +640,21 @@ public abstract class IntentResolver<F extends IntentFilter, R extends Object> { * included here. This also includes the "*" for the "{@literal *}/*" * MIME type. */ - private final HashMap<String, F[]> mWildTypeToFilter = new HashMap<String, F[]>(); + private final ArrayMap<String, F[]> mWildTypeToFilter = new ArrayMap<String, F[]>(); /** * All of the URI schemes (such as http) that have been registered. */ - private final HashMap<String, F[]> mSchemeToFilter = new HashMap<String, F[]>(); + private final ArrayMap<String, F[]> mSchemeToFilter = new ArrayMap<String, F[]>(); /** * All of the actions that have been registered, but only those that did * not specify data. */ - private final HashMap<String, F[]> mActionToFilter = new HashMap<String, F[]>(); + private final ArrayMap<String, F[]> mActionToFilter = new ArrayMap<String, F[]>(); /** * All of the actions that have been registered and specified a MIME type. */ - private final HashMap<String, F[]> mTypedActionToFilter = new HashMap<String, F[]>(); + private final ArrayMap<String, F[]> mTypedActionToFilter = new ArrayMap<String, F[]>(); } diff --git a/services/java/com/android/server/IntentResolverOld.java b/services/java/com/android/server/IntentResolverOld.java deleted file mode 100644 index 94a23796cbb0..000000000000 --- a/services/java/com/android/server/IntentResolverOld.java +++ /dev/null @@ -1,638 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import java.io.PrintWriter; -import java.util.ArrayList; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; - -import android.net.Uri; -import android.util.FastImmutableArraySet; -import android.util.Log; -import android.util.PrintWriterPrinter; -import android.util.Slog; -import android.util.LogPrinter; -import android.util.Printer; - -import android.content.Intent; -import android.content.IntentFilter; - -/** - * Temporary for verification of new implementation. - * {@hide} - */ -public abstract class IntentResolverOld<F extends IntentFilter, R extends Object> { - final private static String TAG = "IntentResolver"; - final private static boolean DEBUG = false; - final private static boolean localLOGV = DEBUG || false; - - public void addFilter(F f) { - if (localLOGV) { - Slog.v(TAG, "Adding filter: " + f); - f.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " "); - Slog.v(TAG, " Building Lookup Maps:"); - } - - mFilters.add(f); - int numS = register_intent_filter(f, f.schemesIterator(), - mSchemeToFilter, " Scheme: "); - int numT = register_mime_types(f, " Type: "); - if (numS == 0 && numT == 0) { - register_intent_filter(f, f.actionsIterator(), - mActionToFilter, " Action: "); - } - if (numT != 0) { - register_intent_filter(f, f.actionsIterator(), - mTypedActionToFilter, " TypedAction: "); - } - } - - public void removeFilter(F f) { - removeFilterInternal(f); - mFilters.remove(f); - } - - void removeFilterInternal(F f) { - if (localLOGV) { - Slog.v(TAG, "Removing filter: " + f); - f.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " "); - Slog.v(TAG, " Cleaning Lookup Maps:"); - } - - int numS = unregister_intent_filter(f, f.schemesIterator(), - mSchemeToFilter, " Scheme: "); - int numT = unregister_mime_types(f, " Type: "); - if (numS == 0 && numT == 0) { - unregister_intent_filter(f, f.actionsIterator(), - mActionToFilter, " Action: "); - } - if (numT != 0) { - unregister_intent_filter(f, f.actionsIterator(), - mTypedActionToFilter, " TypedAction: "); - } - } - - boolean dumpMap(PrintWriter out, String titlePrefix, String title, - String prefix, Map<String, ArrayList<F>> map, String packageName, - boolean printFilter) { - String eprefix = prefix + " "; - String fprefix = prefix + " "; - boolean printedSomething = false; - Printer printer = null; - for (Map.Entry<String, ArrayList<F>> e : map.entrySet()) { - ArrayList<F> a = e.getValue(); - final int N = a.size(); - boolean printedHeader = false; - for (int i=0; i<N; i++) { - F filter = a.get(i); - if (packageName != null && !isPackageForFilter(packageName, filter)) { - continue; - } - if (title != null) { - out.print(titlePrefix); out.println(title); - title = null; - } - if (!printedHeader) { - out.print(eprefix); out.print(e.getKey()); out.println(":"); - printedHeader = true; - } - printedSomething = true; - dumpFilter(out, fprefix, filter); - if (printFilter) { - if (printer == null) { - printer = new PrintWriterPrinter(out); - } - filter.dump(printer, fprefix + " "); - } - } - } - return printedSomething; - } - - public boolean dump(PrintWriter out, String title, String prefix, String packageName, - boolean printFilter) { - String innerPrefix = prefix + " "; - String sepPrefix = "\n" + prefix; - String curPrefix = title + "\n" + prefix; - if (dumpMap(out, curPrefix, "Full MIME Types:", innerPrefix, - mTypeToFilter, packageName, printFilter)) { - curPrefix = sepPrefix; - } - if (dumpMap(out, curPrefix, "Base MIME Types:", innerPrefix, - mBaseTypeToFilter, packageName, printFilter)) { - curPrefix = sepPrefix; - } - if (dumpMap(out, curPrefix, "Wild MIME Types:", innerPrefix, - mWildTypeToFilter, packageName, printFilter)) { - curPrefix = sepPrefix; - } - if (dumpMap(out, curPrefix, "Schemes:", innerPrefix, - mSchemeToFilter, packageName, printFilter)) { - curPrefix = sepPrefix; - } - if (dumpMap(out, curPrefix, "Non-Data Actions:", innerPrefix, - mActionToFilter, packageName, printFilter)) { - curPrefix = sepPrefix; - } - if (dumpMap(out, curPrefix, "MIME Typed Actions:", innerPrefix, - mTypedActionToFilter, packageName, printFilter)) { - curPrefix = sepPrefix; - } - return curPrefix == sepPrefix; - } - - private class IteratorWrapper implements Iterator<F> { - private final Iterator<F> mI; - private F mCur; - - IteratorWrapper(Iterator<F> it) { - mI = it; - } - - public boolean hasNext() { - return mI.hasNext(); - } - - public F next() { - return (mCur = mI.next()); - } - - public void remove() { - if (mCur != null) { - removeFilterInternal(mCur); - } - mI.remove(); - } - - } - - /** - * Returns an iterator allowing filters to be removed. - */ - public Iterator<F> filterIterator() { - return new IteratorWrapper(mFilters.iterator()); - } - - /** - * Returns a read-only set of the filters. - */ - public Set<F> filterSet() { - return Collections.unmodifiableSet(mFilters); - } - - public List<R> queryIntentFromList(Intent intent, String resolvedType, - boolean defaultOnly, ArrayList<ArrayList<F>> listCut, int userId) { - ArrayList<R> resultList = new ArrayList<R>(); - - final boolean debug = localLOGV || - ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); - - FastImmutableArraySet<String> categories = getFastIntentCategories(intent); - final String scheme = intent.getScheme(); - int N = listCut.size(); - for (int i = 0; i < N; ++i) { - buildResolveList(intent, categories, debug, defaultOnly, - resolvedType, scheme, listCut.get(i), resultList, userId); - } - sortResults(resultList); - return resultList; - } - - public List<R> queryIntent(Intent intent, String resolvedType, boolean defaultOnly, - int userId) { - String scheme = intent.getScheme(); - - ArrayList<R> finalList = new ArrayList<R>(); - - final boolean debug = localLOGV || - ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); - - if (debug) Slog.v( - TAG, "Resolving type " + resolvedType + " scheme " + scheme - + " of intent " + intent); - - ArrayList<F> firstTypeCut = null; - ArrayList<F> secondTypeCut = null; - ArrayList<F> thirdTypeCut = null; - ArrayList<F> schemeCut = null; - - // If the intent includes a MIME type, then we want to collect all of - // the filters that match that MIME type. - if (resolvedType != null) { - int slashpos = resolvedType.indexOf('/'); - if (slashpos > 0) { - final String baseType = resolvedType.substring(0, slashpos); - if (!baseType.equals("*")) { - if (resolvedType.length() != slashpos+2 - || resolvedType.charAt(slashpos+1) != '*') { - // Not a wild card, so we can just look for all filters that - // completely match or wildcards whose base type matches. - firstTypeCut = mTypeToFilter.get(resolvedType); - if (debug) Slog.v(TAG, "First type cut: " + firstTypeCut); - secondTypeCut = mWildTypeToFilter.get(baseType); - if (debug) Slog.v(TAG, "Second type cut: " + secondTypeCut); - } else { - // We can match anything with our base type. - firstTypeCut = mBaseTypeToFilter.get(baseType); - if (debug) Slog.v(TAG, "First type cut: " + firstTypeCut); - secondTypeCut = mWildTypeToFilter.get(baseType); - if (debug) Slog.v(TAG, "Second type cut: " + secondTypeCut); - } - // Any */* types always apply, but we only need to do this - // if the intent type was not already */*. - thirdTypeCut = mWildTypeToFilter.get("*"); - if (debug) Slog.v(TAG, "Third type cut: " + thirdTypeCut); - } else if (intent.getAction() != null) { - // The intent specified any type ({@literal *}/*). This - // can be a whole heck of a lot of things, so as a first - // cut let's use the action instead. - firstTypeCut = mTypedActionToFilter.get(intent.getAction()); - if (debug) Slog.v(TAG, "Typed Action list: " + firstTypeCut); - } - } - } - - // If the intent includes a data URI, then we want to collect all of - // the filters that match its scheme (we will further refine matches - // on the authority and path by directly matching each resulting filter). - if (scheme != null) { - schemeCut = mSchemeToFilter.get(scheme); - if (debug) Slog.v(TAG, "Scheme list: " + schemeCut); - } - - // If the intent does not specify any data -- either a MIME type or - // a URI -- then we will only be looking for matches against empty - // data. - if (resolvedType == null && scheme == null && intent.getAction() != null) { - firstTypeCut = mActionToFilter.get(intent.getAction()); - if (debug) Slog.v(TAG, "Action list: " + firstTypeCut); - } - - FastImmutableArraySet<String> categories = getFastIntentCategories(intent); - if (firstTypeCut != null) { - buildResolveList(intent, categories, debug, defaultOnly, - resolvedType, scheme, firstTypeCut, finalList, userId); - } - if (secondTypeCut != null) { - buildResolveList(intent, categories, debug, defaultOnly, - resolvedType, scheme, secondTypeCut, finalList, userId); - } - if (thirdTypeCut != null) { - buildResolveList(intent, categories, debug, defaultOnly, - resolvedType, scheme, thirdTypeCut, finalList, userId); - } - if (schemeCut != null) { - buildResolveList(intent, categories, debug, defaultOnly, - resolvedType, scheme, schemeCut, finalList, userId); - } - sortResults(finalList); - - if (debug) { - Slog.v(TAG, "Final result list:"); - for (R r : finalList) { - Slog.v(TAG, " " + r); - } - } - return finalList; - } - - /** - * Control whether the given filter is allowed to go into the result - * list. Mainly intended to prevent adding multiple filters for the - * same target object. - */ - protected boolean allowFilterResult(F filter, List<R> dest) { - return true; - } - - /** - * Returns whether the object associated with the given filter is - * "stopped," that is whether it should not be included in the result - * if the intent requests to excluded stopped objects. - */ - protected boolean isFilterStopped(F filter, int userId) { - return false; - } - - /** - * Returns whether this filter is owned by this package. This must be - * implemented to provide correct filtering of Intents that have - * specified a package name they are to be delivered to. - */ - protected abstract boolean isPackageForFilter(String packageName, F filter); - - @SuppressWarnings("unchecked") - protected R newResult(F filter, int match, int userId) { - return (R)filter; - } - - @SuppressWarnings("unchecked") - protected void sortResults(List<R> results) { - Collections.sort(results, mResolvePrioritySorter); - } - - protected void dumpFilter(PrintWriter out, String prefix, F filter) { - out.print(prefix); out.println(filter); - } - - private final int register_mime_types(F filter, String prefix) { - final Iterator<String> i = filter.typesIterator(); - if (i == null) { - return 0; - } - - int num = 0; - while (i.hasNext()) { - String name = i.next(); - num++; - if (localLOGV) Slog.v(TAG, prefix + name); - String baseName = name; - final int slashpos = name.indexOf('/'); - if (slashpos > 0) { - baseName = name.substring(0, slashpos).intern(); - } else { - name = name + "/*"; - } - - ArrayList<F> array = mTypeToFilter.get(name); - if (array == null) { - //Slog.v(TAG, "Creating new array for " + name); - array = new ArrayList<F>(); - mTypeToFilter.put(name, array); - } - array.add(filter); - - if (slashpos > 0) { - array = mBaseTypeToFilter.get(baseName); - if (array == null) { - //Slog.v(TAG, "Creating new array for " + name); - array = new ArrayList<F>(); - mBaseTypeToFilter.put(baseName, array); - } - array.add(filter); - } else { - array = mWildTypeToFilter.get(baseName); - if (array == null) { - //Slog.v(TAG, "Creating new array for " + name); - array = new ArrayList<F>(); - mWildTypeToFilter.put(baseName, array); - } - array.add(filter); - } - } - - return num; - } - - private final int unregister_mime_types(F filter, String prefix) { - final Iterator<String> i = filter.typesIterator(); - if (i == null) { - return 0; - } - - int num = 0; - while (i.hasNext()) { - String name = i.next(); - num++; - if (localLOGV) Slog.v(TAG, prefix + name); - String baseName = name; - final int slashpos = name.indexOf('/'); - if (slashpos > 0) { - baseName = name.substring(0, slashpos).intern(); - } else { - name = name + "/*"; - } - - if (!remove_all_objects(mTypeToFilter.get(name), filter)) { - mTypeToFilter.remove(name); - } - - if (slashpos > 0) { - if (!remove_all_objects(mBaseTypeToFilter.get(baseName), filter)) { - mBaseTypeToFilter.remove(baseName); - } - } else { - if (!remove_all_objects(mWildTypeToFilter.get(baseName), filter)) { - mWildTypeToFilter.remove(baseName); - } - } - } - return num; - } - - private final int register_intent_filter(F filter, Iterator<String> i, - HashMap<String, ArrayList<F>> dest, String prefix) { - if (i == null) { - return 0; - } - - int num = 0; - while (i.hasNext()) { - String name = i.next(); - num++; - if (localLOGV) Slog.v(TAG, prefix + name); - ArrayList<F> array = dest.get(name); - if (array == null) { - //Slog.v(TAG, "Creating new array for " + name); - array = new ArrayList<F>(); - dest.put(name, array); - } - array.add(filter); - } - return num; - } - - private final int unregister_intent_filter(F filter, Iterator<String> i, - HashMap<String, ArrayList<F>> dest, String prefix) { - if (i == null) { - return 0; - } - - int num = 0; - while (i.hasNext()) { - String name = i.next(); - num++; - if (localLOGV) Slog.v(TAG, prefix + name); - if (!remove_all_objects(dest.get(name), filter)) { - dest.remove(name); - } - } - return num; - } - - private final boolean remove_all_objects(List<F> list, Object object) { - if (list != null) { - int N = list.size(); - for (int idx=0; idx<N; idx++) { - if (list.get(idx) == object) { - list.remove(idx); - idx--; - N--; - } - } - return N > 0; - } - return false; - } - - private static FastImmutableArraySet<String> getFastIntentCategories(Intent intent) { - final Set<String> categories = intent.getCategories(); - if (categories == null) { - return null; - } - return new FastImmutableArraySet<String>(categories.toArray(new String[categories.size()])); - } - - private void buildResolveList(Intent intent, FastImmutableArraySet<String> categories, - boolean debug, boolean defaultOnly, - String resolvedType, String scheme, List<F> src, List<R> dest, int userId) { - final String action = intent.getAction(); - final Uri data = intent.getData(); - final String packageName = intent.getPackage(); - - final boolean excludingStopped = intent.isExcludingStopped(); - - final int N = src != null ? src.size() : 0; - boolean hasNonDefaults = false; - int i; - for (i=0; i<N; i++) { - F filter = src.get(i); - int match; - if (debug) Slog.v(TAG, "Matching against filter " + filter); - - if (excludingStopped && isFilterStopped(filter, userId)) { - if (debug) { - Slog.v(TAG, " Filter's target is stopped; skipping"); - } - continue; - } - - // Is delivery being limited to filters owned by a particular package? - if (packageName != null && !isPackageForFilter(packageName, filter)) { - if (debug) { - Slog.v(TAG, " Filter is not from package " + packageName + "; skipping"); - } - continue; - } - - // Do we already have this one? - if (!allowFilterResult(filter, dest)) { - if (debug) { - Slog.v(TAG, " Filter's target already added"); - } - continue; - } - - match = filter.match(action, resolvedType, scheme, data, categories, TAG); - if (match >= 0) { - if (debug) Slog.v(TAG, " Filter matched! match=0x" + - Integer.toHexString(match)); - if (!defaultOnly || filter.hasCategory(Intent.CATEGORY_DEFAULT)) { - final R oneResult = newResult(filter, match, userId); - if (oneResult != null) { - dest.add(oneResult); - } - } else { - hasNonDefaults = true; - } - } else { - if (debug) { - String reason; - switch (match) { - case IntentFilter.NO_MATCH_ACTION: reason = "action"; break; - case IntentFilter.NO_MATCH_CATEGORY: reason = "category"; break; - case IntentFilter.NO_MATCH_DATA: reason = "data"; break; - case IntentFilter.NO_MATCH_TYPE: reason = "type"; break; - default: reason = "unknown reason"; break; - } - Slog.v(TAG, " Filter did not match: " + reason); - } - } - } - - if (dest.size() == 0 && hasNonDefaults) { - Slog.w(TAG, "resolveIntent failed: found match, but none with Intent.CATEGORY_DEFAULT"); - } - } - - // Sorts a List of IntentFilter objects into descending priority order. - @SuppressWarnings("rawtypes") - private static final Comparator mResolvePrioritySorter = new Comparator() { - public int compare(Object o1, Object o2) { - final int q1 = ((IntentFilter) o1).getPriority(); - final int q2 = ((IntentFilter) o2).getPriority(); - return (q1 > q2) ? -1 : ((q1 < q2) ? 1 : 0); - } - }; - - /** - * All filters that have been registered. - */ - final HashSet<F> mFilters = new HashSet<F>(); - - /** - * All of the MIME types that have been registered, such as "image/jpeg", - * "image/*", or "{@literal *}/*". - */ - final HashMap<String, ArrayList<F>> mTypeToFilter - = new HashMap<String, ArrayList<F>>(); - - /** - * The base names of all of all fully qualified MIME types that have been - * registered, such as "image" or "*". Wild card MIME types such as - * "image/*" will not be here. - */ - final HashMap<String, ArrayList<F>> mBaseTypeToFilter - = new HashMap<String, ArrayList<F>>(); - - /** - * The base names of all of the MIME types with a sub-type wildcard that - * have been registered. For example, a filter with "image/*" will be - * included here as "image" but one with "image/jpeg" will not be - * included here. This also includes the "*" for the "{@literal *}/*" - * MIME type. - */ - final HashMap<String, ArrayList<F>> mWildTypeToFilter - = new HashMap<String, ArrayList<F>>(); - - /** - * All of the URI schemes (such as http) that have been registered. - */ - final HashMap<String, ArrayList<F>> mSchemeToFilter - = new HashMap<String, ArrayList<F>>(); - - /** - * All of the actions that have been registered, but only those that did - * not specify data. - */ - final HashMap<String, ArrayList<F>> mActionToFilter - = new HashMap<String, ArrayList<F>>(); - - /** - * All of the actions that have been registered and specified a MIME type. - */ - final HashMap<String, ArrayList<F>> mTypedActionToFilter - = new HashMap<String, ArrayList<F>>(); -} - diff --git a/services/java/com/android/server/IoThread.java b/services/java/com/android/server/IoThread.java new file mode 100644 index 000000000000..09f2af79c83d --- /dev/null +++ b/services/java/com/android/server/IoThread.java @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.os.Handler; +import android.os.HandlerThread; + +/** + * Shared singleton I/O thread for the system. This is a thread for non-background + * service operations that can potential block briefly on network IO operations + * (not waiting for data itself, but communicating with network daemons). + */ +public final class IoThread extends HandlerThread { + private static IoThread sInstance; + private static Handler sHandler; + + private IoThread() { + super("android.io", android.os.Process.THREAD_PRIORITY_DEFAULT); + } + + private static void ensureThreadLocked() { + if (sInstance == null) { + sInstance = new IoThread(); + sInstance.start(); + sHandler = new Handler(sInstance.getLooper()); + sHandler.post(new Runnable() { + @Override + public void run() { + android.os.Process.setCanSelfBackground(false); + } + }); + } + } + + public static IoThread get() { + synchronized (IoThread.class) { + ensureThreadLocked(); + return sInstance; + } + } + + public static Handler getHandler() { + synchronized (IoThread.class) { + ensureThreadLocked(); + return sHandler; + } + } +} diff --git a/services/java/com/android/server/LocationManagerService.java b/services/java/com/android/server/LocationManagerService.java index f784030d94c6..8f480ddbf514 100644 --- a/services/java/com/android/server/LocationManagerService.java +++ b/services/java/com/android/server/LocationManagerService.java @@ -47,7 +47,6 @@ import android.location.LocationRequest; import android.os.Binder; import android.os.Bundle; import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; @@ -63,6 +62,9 @@ import android.util.Slog; import com.android.internal.content.PackageMonitor; import com.android.internal.location.ProviderProperties; import com.android.internal.location.ProviderRequest; +import com.android.internal.os.BackgroundThread; +import com.android.server.location.FlpHardwareProvider; +import com.android.server.location.FusedProxy; import com.android.server.location.GeocoderProxy; import com.android.server.location.GeofenceProxy; import com.android.server.location.GeofenceManager; @@ -93,7 +95,6 @@ public class LocationManagerService extends ILocationManager.Stub { public static final boolean D = Log.isLoggable(TAG, Log.DEBUG); private static final String WAKELOCK_KEY = TAG; - private static final String THREAD_NAME = TAG; // Location resolution level: no location data whatsoever private static final int RESOLUTION_LEVEL_NONE = 0; @@ -110,7 +111,7 @@ public class LocationManagerService extends ILocationManager.Stub { android.Manifest.permission.INSTALL_LOCATION_PROVIDER; private static final String NETWORK_LOCATION_SERVICE_ACTION = - "com.android.location.service.v2.NetworkLocationProvider"; + "com.android.location.service.v3.NetworkLocationProvider"; private static final String FUSED_LOCATION_SERVICE_ACTION = "com.android.location.service.FusedLocationProvider"; @@ -118,6 +119,9 @@ public class LocationManagerService extends ILocationManager.Stub { private static final long NANOS_PER_MILLI = 1000000L; + // The maximum interval a location request can have and still be considered "high power". + private static final long HIGH_POWER_INTERVAL_MS = 5 * 60 * 1000; + // Location Providers may sometimes deliver location updates // slightly faster that requested - provide grace period so // we don't unnecessarily filter events that are otherwise on @@ -143,7 +147,6 @@ public class LocationManagerService extends ILocationManager.Stub { private LocationWorkerHandler mLocationHandler; private PassiveProvider mPassiveProvider; // track passive provider for special cases private LocationBlacklist mBlacklist; - private HandlerThread mHandlerThread; // --- fields below are protected by mLock --- // Set of providers that are explicitly enabled @@ -200,7 +203,7 @@ public class LocationManagerService extends ILocationManager.Stub { // most startup is deferred until systemReady() } - public void systemReady() { + public void systemRunning() { synchronized (mLock) { if (D) Log.d(TAG, "systemReady()"); @@ -211,9 +214,7 @@ public class LocationManagerService extends ILocationManager.Stub { mPowerManager = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE); // prepare worker thread - mHandlerThread = new HandlerThread(THREAD_NAME, Process.THREAD_PRIORITY_BACKGROUND); - mHandlerThread.start(); - mLocationHandler = new LocationWorkerHandler(mHandlerThread.getLooper()); + mLocationHandler = new LocationWorkerHandler(BackgroundThread.get().getLooper()); // prepare mLocationHandler's dependents mLocationFudger = new LocationFudger(mContext, mLocationHandler); @@ -222,9 +223,13 @@ public class LocationManagerService extends ILocationManager.Stub { mGeofenceManager = new GeofenceManager(mContext, mBlacklist); // Monitor for app ops mode changes. - AppOpsManager.Callback callback = new AppOpsManager.Callback() { - public void opChanged(int op, String packageName) { + AppOpsManager.OnOpChangedListener callback + = new AppOpsManager.OnOpChangedInternalListener() { + public void onOpChanged(int op, String packageName) { synchronized (mLock) { + for (Receiver receiver : mReceivers.values()) { + receiver.updateMonitoring(true); + } applyAllProviderRequirementsLocked(); } } @@ -416,17 +421,30 @@ public class LocationManagerService extends ILocationManager.Stub { Slog.e(TAG, "no geocoder provider found"); } + // bind to fused provider + FlpHardwareProvider flpHardwareProvider = FlpHardwareProvider.getInstance(mContext); + FusedProxy fusedProxy = FusedProxy.createAndBind( + mContext, + mLocationHandler, + flpHardwareProvider.getLocationHardware(), + com.android.internal.R.bool.config_enableFusedLocationOverlay, + com.android.internal.R.string.config_fusedLocationProviderPackageName, + com.android.internal.R.array.config_locationProviderPackageNames); + if(fusedProxy == null) { + Slog.e(TAG, "No FusedProvider found."); + } + // bind to geofence provider GeofenceProxy provider = GeofenceProxy.createAndBind(mContext, com.android.internal.R.bool.config_enableGeofenceOverlay, com.android.internal.R.string.config_geofenceProviderPackageName, com.android.internal.R.array.config_locationProviderPackageNames, mLocationHandler, - gpsProvider.getGpsGeofenceProxy()); + gpsProvider.getGpsGeofenceProxy(), + flpHardwareProvider.getGeofenceHardware()); if (provider == null) { Slog.e(TAG, "no geofence provider found"); } - } /** @@ -459,15 +477,21 @@ public class LocationManagerService extends ILocationManager.Stub { final ILocationListener mListener; final PendingIntent mPendingIntent; + final WorkSource mWorkSource; // WorkSource for battery blame, or null to assign to caller. + final boolean mHideFromAppOps; // True if AppOps should not monitor this receiver. final Object mKey; final HashMap<String,UpdateRecord> mUpdateRecords = new HashMap<String,UpdateRecord>(); + // True if app ops has started monitoring this receiver for locations. + boolean mOpMonitoring; + // True if app ops has started monitoring this receiver for high power (gps) locations. + boolean mOpHighPowerMonitoring; int mPendingBroadcasts; PowerManager.WakeLock mWakeLock; Receiver(ILocationListener listener, PendingIntent intent, int pid, int uid, - String packageName) { + String packageName, WorkSource workSource, boolean hideFromAppOps) { mListener = listener; mPendingIntent = intent; if (listener != null) { @@ -479,10 +503,20 @@ public class LocationManagerService extends ILocationManager.Stub { mUid = uid; mPid = pid; mPackageName = packageName; + if (workSource != null && workSource.size() <= 0) { + workSource = null; + } + mWorkSource = workSource; + mHideFromAppOps = hideFromAppOps; + + updateMonitoring(true); // construct/configure wakelock mWakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKELOCK_KEY); - mWakeLock.setWorkSource(new WorkSource(mUid, mPackageName)); + if (workSource == null) { + workSource = new WorkSource(mUid, mPackageName); + } + mWakeLock.setWorkSource(workSource); } @Override @@ -515,6 +549,83 @@ public class LocationManagerService extends ILocationManager.Stub { return s.toString(); } + /** + * Update AppOp monitoring for this receiver. + * + * @param allow If true receiver is currently active, if false it's been removed. + */ + public void updateMonitoring(boolean allow) { + if (mHideFromAppOps) { + return; + } + + boolean requestingLocation = false; + boolean requestingHighPowerLocation = false; + if (allow) { + // See if receiver has any enabled update records. Also note if any update records + // are high power (has a high power provider with an interval under a threshold). + for (UpdateRecord updateRecord : mUpdateRecords.values()) { + if (isAllowedByCurrentUserSettingsLocked(updateRecord.mProvider)) { + requestingLocation = true; + LocationProviderInterface locationProvider + = mProvidersByName.get(updateRecord.mProvider); + ProviderProperties properties = locationProvider != null + ? locationProvider.getProperties() : null; + if (properties != null + && properties.mPowerRequirement == Criteria.POWER_HIGH + && updateRecord.mRequest.getInterval() < HIGH_POWER_INTERVAL_MS) { + requestingHighPowerLocation = true; + break; + } + } + } + } + + // First update monitoring of any location request (including high power). + mOpMonitoring = updateMonitoring( + requestingLocation, + mOpMonitoring, + AppOpsManager.OP_MONITOR_LOCATION); + + // Now update monitoring of high power requests only. + boolean wasHighPowerMonitoring = mOpHighPowerMonitoring; + mOpHighPowerMonitoring = updateMonitoring( + requestingHighPowerLocation, + mOpHighPowerMonitoring, + AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION); + if (mOpHighPowerMonitoring != wasHighPowerMonitoring) { + // Send an intent to notify that a high power request has been added/removed. + Intent intent = new Intent(LocationManager.HIGH_POWER_REQUEST_CHANGE_ACTION); + mContext.sendBroadcastAsUser(intent, UserHandle.ALL); + } + } + + /** + * Update AppOps monitoring for a single location request and op type. + * + * @param allowMonitoring True if monitoring is allowed for this request/op. + * @param currentlyMonitoring True if AppOps is currently monitoring this request/op. + * @param op AppOps code for the op to update. + * @return True if monitoring is on for this request/op after updating. + */ + private boolean updateMonitoring(boolean allowMonitoring, boolean currentlyMonitoring, + int op) { + if (!currentlyMonitoring) { + if (allowMonitoring) { + return mAppOps.startOpNoThrow(op, mUid, mPackageName) + == AppOpsManager.MODE_ALLOWED; + } + } else { + if (!allowMonitoring || mAppOps.checkOpNoThrow(op, mUid, mPackageName) + != AppOpsManager.MODE_ALLOWED) { + mAppOps.finishOp(op, mUid, mPackageName); + return false; + } + } + + return currentlyMonitoring; + } + public boolean isListener() { return mListener != null; } @@ -600,6 +711,10 @@ public class LocationManagerService extends ILocationManager.Stub { } public boolean callProviderEnabledLocked(String provider, boolean enabled) { + // First update AppOp monitoring. + // An app may get/lose location access as providers are enabled/disabled. + updateMonitoring(true); + if (mListener != null) { try { synchronized (this) { @@ -866,6 +981,20 @@ public class LocationManagerService extends ILocationManager.Stub { } } + /** + * Throw SecurityException if WorkSource use is not allowed (i.e. can't blame other packages + * for battery). + */ + private void checkDeviceStatsAllowed() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.UPDATE_DEVICE_STATS, null); + } + + private void checkUpdateAppOpsAllowed() { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.UPDATE_APP_OPS_STATS, null); + } + public static int resolutionLevelToOp(int allowedResolutionLevel) { if (allowedResolutionLevel != RESOLUTION_LEVEL_NONE) { if (allowedResolutionLevel == RESOLUTION_LEVEL_COARSE) { @@ -1028,6 +1157,8 @@ public class LocationManagerService extends ILocationManager.Stub { if (changesMade) { mContext.sendBroadcastAsUser(new Intent(LocationManager.PROVIDERS_CHANGED_ACTION), UserHandle.ALL); + mContext.sendBroadcastAsUser(new Intent(LocationManager.MODE_CHANGED_ACTION), + UserHandle.ALL); } } @@ -1107,7 +1238,18 @@ public class LocationManagerService extends ILocationManager.Stub { if (UserHandle.getUserId(record.mReceiver.mUid) == mCurrentUserId) { LocationRequest locationRequest = record.mRequest; if (locationRequest.getInterval() <= thresholdInterval) { - worksource.add(record.mReceiver.mUid, record.mReceiver.mPackageName); + if (record.mReceiver.mWorkSource != null + && record.mReceiver.mWorkSource.size() > 0 + && record.mReceiver.mWorkSource.getName(0) != null) { + // Assign blame to another work source. + // Can only assign blame if the WorkSource contains names. + worksource.add(record.mReceiver.mWorkSource); + } else { + // Assign blame to caller. + worksource.add( + record.mReceiver.mUid, + record.mReceiver.mPackageName); + } } } } @@ -1182,11 +1324,12 @@ public class LocationManagerService extends ILocationManager.Stub { } private Receiver getReceiverLocked(ILocationListener listener, int pid, int uid, - String packageName) { + String packageName, WorkSource workSource, boolean hideFromAppOps) { IBinder binder = listener.asBinder(); Receiver receiver = mReceivers.get(binder); if (receiver == null) { - receiver = new Receiver(listener, null, pid, uid, packageName); + receiver = new Receiver(listener, null, pid, uid, packageName, workSource, + hideFromAppOps); mReceivers.put(binder, receiver); try { @@ -1199,10 +1342,12 @@ public class LocationManagerService extends ILocationManager.Stub { return receiver; } - private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName) { + private Receiver getReceiverLocked(PendingIntent intent, int pid, int uid, String packageName, + WorkSource workSource, boolean hideFromAppOps) { Receiver receiver = mReceivers.get(intent); if (receiver == null) { - receiver = new Receiver(null, intent, pid, uid, packageName); + receiver = new Receiver(null, intent, pid, uid, packageName, workSource, + hideFromAppOps); mReceivers.put(intent, receiver); } return receiver; @@ -1264,16 +1409,16 @@ public class LocationManagerService extends ILocationManager.Stub { } private Receiver checkListenerOrIntentLocked(ILocationListener listener, PendingIntent intent, - int pid, int uid, String packageName) { + int pid, int uid, String packageName, WorkSource workSource, boolean hideFromAppOps) { if (intent == null && listener == null) { throw new IllegalArgumentException("need either listener or intent"); } else if (intent != null && listener != null) { throw new IllegalArgumentException("cannot register both listener and intent"); } else if (intent != null) { checkPendingIntent(intent); - return getReceiverLocked(intent, pid, uid, packageName); + return getReceiverLocked(intent, pid, uid, packageName, workSource, hideFromAppOps); } else { - return getReceiverLocked(listener, pid, uid, packageName); + return getReceiverLocked(listener, pid, uid, packageName, workSource, hideFromAppOps); } } @@ -1285,6 +1430,14 @@ public class LocationManagerService extends ILocationManager.Stub { int allowedResolutionLevel = getCallerAllowedResolutionLevel(); checkResolutionLevelIsSufficientForProviderUse(allowedResolutionLevel, request.getProvider()); + WorkSource workSource = request.getWorkSource(); + if (workSource != null && workSource.size() > 0) { + checkDeviceStatsAllowed(); + } + boolean hideFromAppOps = request.getHideFromAppOps(); + if (hideFromAppOps) { + checkUpdateAppOpsAllowed(); + } LocationRequest sanitizedRequest = createSanitizedRequest(request, allowedResolutionLevel); final int pid = Binder.getCallingPid(); @@ -1298,7 +1451,7 @@ public class LocationManagerService extends ILocationManager.Stub { synchronized (mLock) { Receiver recevier = checkListenerOrIntentLocked(listener, intent, pid, uid, - packageName); + packageName, workSource, hideFromAppOps); requestLocationUpdatesLocked(sanitizedRequest, recevier, pid, uid, packageName); } } finally { @@ -1320,7 +1473,7 @@ public class LocationManagerService extends ILocationManager.Stub { + " " + name + " " + request + " from " + packageName + "(" + uid + ")"); LocationProviderInterface provider = mProvidersByName.get(name); if (provider == null) { - throw new IllegalArgumentException("provider doesn't exisit: " + provider); + throw new IllegalArgumentException("provider doesn't exist: " + name); } UpdateRecord record = new UpdateRecord(name, request, receiver); @@ -1336,6 +1489,9 @@ public class LocationManagerService extends ILocationManager.Stub { // Notify the listener that updates are currently disabled receiver.callProviderEnabledLocked(name, false); } + // Update the monitoring here just in case multiple location requests were added to the + // same receiver (this request may be high power and the initial might not have been). + receiver.updateMonitoring(true); } @Override @@ -1347,7 +1503,10 @@ public class LocationManagerService extends ILocationManager.Stub { final int uid = Binder.getCallingUid(); synchronized (mLock) { - Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid, packageName); + WorkSource workSource = null; + boolean hideFromAppOps = false; + Receiver receiver = checkListenerOrIntentLocked(listener, intent, pid, uid, + packageName, workSource, hideFromAppOps); // providers may use public location API's, need to clear identity long identity = Binder.clearCallingIdentity(); @@ -1369,6 +1528,8 @@ public class LocationManagerService extends ILocationManager.Stub { } } + receiver.updateMonitoring(false); + // Record which providers were associated with this listener HashSet<String> providers = new HashSet<String>(); HashMap<String, UpdateRecord> oldRecords = receiver.mUpdateRecords; @@ -1613,8 +1774,12 @@ public class LocationManagerService extends ILocationManager.Stub { @Override public boolean isProviderEnabled(String provider) { + // TODO: remove this check in next release, see b/10696351 checkResolutionLevelIsSufficientForProviderUse(getCallerAllowedResolutionLevel(), provider); + + // Fused provider is accessed indirectly via criteria rather than the provider-based APIs, + // so we discourage its use if (LocationManager.FUSED_PROVIDER.equals(provider)) return false; int uid = Binder.getCallingUid(); diff --git a/services/java/com/android/server/LockSettingsService.java b/services/java/com/android/server/LockSettingsService.java index d52a8e2b3b9b..cd746cf44823 100644 --- a/services/java/com/android/server/LockSettingsService.java +++ b/services/java/com/android/server/LockSettingsService.java @@ -16,6 +16,7 @@ package com.android.server; +import android.app.ActivityManagerNative; import android.content.ContentResolver; import android.content.ContentValues; import android.content.Context; @@ -27,6 +28,9 @@ import static android.Manifest.permission.READ_PROFILE; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteStatement; +import android.media.AudioManager; +import android.media.AudioService; import android.os.Binder; import android.os.Environment; import android.os.RemoteException; @@ -36,7 +40,9 @@ import android.os.UserManager; import android.provider.Settings; import android.provider.Settings.Secure; import android.provider.Settings.SettingNotFoundException; +import android.security.KeyStore; import android.text.TextUtils; +import android.util.Log; import android.util.Slog; import com.android.internal.widget.ILockSettings; @@ -57,6 +63,7 @@ import java.util.List; */ public class LockSettingsService extends ILockSettings.Stub { + private static final String PERMISSION = "android.permission.ACCESS_KEYGUARD_SECURE_STORAGE"; private final DatabaseHelper mOpenHelper; private static final String TAG = "LockSettingsService"; @@ -74,11 +81,14 @@ public class LockSettingsService extends ILockSettings.Stub { private static final String LOCK_PASSWORD_FILE = "password.key"; private final Context mContext; + private LockPatternUtils mLockPatternUtils; public LockSettingsService(Context context) { mContext = context; // Open the database mOpenHelper = new DatabaseHelper(mContext); + + mLockPatternUtils = new LockPatternUtils(context); } public void systemReady() { @@ -143,20 +153,12 @@ public class LockSettingsService extends ILockSettings.Stub { } } - private static final void checkWritePermission(int userId) { - final int callingUid = Binder.getCallingUid(); - if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) { - throw new SecurityException("uid=" + callingUid - + " not authorized to write lock settings"); - } + private final void checkWritePermission(int userId) { + mContext.checkCallingOrSelfPermission(PERMISSION); } - private static final void checkPasswordReadPermission(int userId) { - final int callingUid = Binder.getCallingUid(); - if (UserHandle.getAppId(callingUid) != android.os.Process.SYSTEM_UID) { - throw new SecurityException("uid=" + callingUid - + " not authorized to read lock password"); - } + private final void checkPasswordReadPermission(int userId) { + mContext.checkCallingOrSelfPermission(PERMISSION); } private final void checkReadPermission(String requestedKey, int userId) { @@ -257,15 +259,42 @@ public class LockSettingsService extends ILockSettings.Stub { return new File(getLockPatternFilename(userId)).length() > 0; } + private void maybeUpdateKeystore(String password, int userId) { + if (userId == UserHandle.USER_OWNER) { + final KeyStore keyStore = KeyStore.getInstance(); + // Conditionally reset the keystore if empty. If non-empty, we are just + // switching key guard type + if (TextUtils.isEmpty(password) && keyStore.isEmpty()) { + keyStore.reset(); + } else { + // Update the keystore password + keyStore.password(password); + } + } + } + @Override - public void setLockPattern(byte[] hash, int userId) throws RemoteException { + public void setLockPattern(String pattern, int userId) throws RemoteException { checkWritePermission(userId); + maybeUpdateKeystore(pattern, userId); + + final byte[] hash = LockPatternUtils.patternToHash( + LockPatternUtils.stringToPattern(pattern)); writeFile(getLockPatternFilename(userId), hash); } @Override - public boolean checkPattern(byte[] hash, int userId) throws RemoteException { + public void setLockPassword(String password, int userId) throws RemoteException { + checkWritePermission(userId); + + maybeUpdateKeystore(password, userId); + + writeFile(getLockPasswordFilename(userId), mLockPatternUtils.passwordToHash(password)); + } + + @Override + public boolean checkPattern(String pattern, int userId) throws RemoteException { checkPasswordReadPermission(userId); try { // Read all the bytes from the file @@ -277,25 +306,23 @@ public class LockSettingsService extends ILockSettings.Stub { return true; } // Compare the hash from the file with the entered pattern's hash - return Arrays.equals(stored, hash); + final byte[] hash = LockPatternUtils.patternToHash( + LockPatternUtils.stringToPattern(pattern)); + final boolean matched = Arrays.equals(stored, hash); + if (matched && !TextUtils.isEmpty(pattern)) { + maybeUpdateKeystore(pattern, userId); + } + return matched; } catch (FileNotFoundException fnfe) { Slog.e(TAG, "Cannot read file " + fnfe); - return true; } catch (IOException ioe) { Slog.e(TAG, "Cannot read file " + ioe); - return true; } + return true; } @Override - public void setLockPassword(byte[] hash, int userId) throws RemoteException { - checkWritePermission(userId); - - writeFile(getLockPasswordFilename(userId), hash); - } - - @Override - public boolean checkPassword(byte[] hash, int userId) throws RemoteException { + public boolean checkPassword(String password, int userId) throws RemoteException { checkPasswordReadPermission(userId); try { @@ -308,14 +335,18 @@ public class LockSettingsService extends ILockSettings.Stub { return true; } // Compare the hash from the file with the entered password's hash - return Arrays.equals(stored, hash); + final byte[] hash = mLockPatternUtils.passwordToHash(password); + final boolean matched = Arrays.equals(stored, hash); + if (matched && !TextUtils.isEmpty(password)) { + maybeUpdateKeystore(password, userId); + } + return matched; } catch (FileNotFoundException fnfe) { Slog.e(TAG, "Cannot read file " + fnfe); - return true; } catch (IOException ioe) { Slog.e(TAG, "Cannot read file " + ioe); - return true; } + return true; } @Override @@ -398,7 +429,7 @@ public class LockSettingsService extends ILockSettings.Stub { private static final String TAG = "LockSettingsDB"; private static final String DATABASE_NAME = "locksettings.db"; - private static final int DATABASE_VERSION = 1; + private static final int DATABASE_VERSION = 2; public DatabaseHelper(Context context) { super(context, DATABASE_NAME, null, DATABASE_VERSION); @@ -431,7 +462,44 @@ public class LockSettingsService extends ILockSettings.Stub { @Override public void onUpgrade(SQLiteDatabase db, int oldVersion, int currentVersion) { - // Nothing yet + int upgradeVersion = oldVersion; + if (upgradeVersion == 1) { + // Set the initial value for {@link LockPatternUtils#LOCKSCREEN_WIDGETS_ENABLED} + // during upgrade based on whether each user previously had widgets in keyguard. + maybeEnableWidgetSettingForUsers(db); + upgradeVersion = 2; + } + + if (upgradeVersion != DATABASE_VERSION) { + Log.w(TAG, "Failed to upgrade database!"); + } + } + + private void maybeEnableWidgetSettingForUsers(SQLiteDatabase db) { + final UserManager um = (UserManager) mContext.getSystemService(USER_SERVICE); + final ContentResolver cr = mContext.getContentResolver(); + final List<UserInfo> users = um.getUsers(); + for (int i = 0; i < users.size(); i++) { + final int userId = users.get(i).id; + final boolean enabled = mLockPatternUtils.hasWidgetsEnabledInKeyguard(userId); + Log.v(TAG, "Widget upgrade uid=" + userId + ", enabled=" + + enabled + ", w[]=" + mLockPatternUtils.getAppWidgets()); + loadSetting(db, LockPatternUtils.LOCKSCREEN_WIDGETS_ENABLED, userId, enabled); + } + } + + private void loadSetting(SQLiteDatabase db, String key, int userId, boolean value) { + SQLiteStatement stmt = null; + try { + stmt = db.compileStatement( + "INSERT OR REPLACE INTO locksettings(name,user,value) VALUES(?,?,?);"); + stmt.bindString(1, key); + stmt.bindLong(2, userId); + stmt.bindLong(3, value ? 1 : 0); + stmt.execute(); + } finally { + if (stmt != null) stmt.close(); + } } } diff --git a/services/java/com/android/server/MountService.java b/services/java/com/android/server/MountService.java index e670ef9ba621..c7ca1ea9cb3f 100644 --- a/services/java/com/android/server/MountService.java +++ b/services/java/com/android/server/MountService.java @@ -19,6 +19,7 @@ package com.android.server; import static android.content.pm.PackageManager.PERMISSION_GRANTED; import android.Manifest; +import android.app.AppOpsManager; import android.content.BroadcastReceiver; import android.content.ComponentName; import android.content.Context; @@ -190,6 +191,8 @@ class MountService extends IMountService.Stub /** When defined, base template for user-specific {@link StorageVolume}. */ private StorageVolume mEmulatedTemplate; + // TODO: separate storage volumes on per-user basis + @GuardedBy("mVolumesLock") private final ArrayList<StorageVolume> mVolumes = Lists.newArrayList(); /** Map from path to {@link StorageVolume} */ @@ -492,7 +495,6 @@ class MountService extends IMountService.Stub } }; - private final HandlerThread mHandlerThread; private final Handler mHandler; void waitForAsecScan() { @@ -828,7 +830,7 @@ class MountService extends IMountService.Stub } if (code == VoldResponseCode.VolumeDiskInserted) { - new Thread() { + new Thread("MountService#VolumeDiskInserted") { @Override public void run() { try { @@ -1115,7 +1117,7 @@ class MountService extends IMountService.Stub /* * USB mass storage disconnected while enabled */ - new Thread() { + new Thread("MountService#AvailabilityChange") { @Override public void run() { try { @@ -1225,6 +1227,9 @@ class MountService extends IMountService.Stub descriptionId, primary, removable, emulated, mtpReserve, allowMassStorage, maxFileSize, null); addVolumeLocked(volume); + + // Until we hear otherwise, treat as unmounted + mVolumeStates.put(volume.getPath(), Environment.MEDIA_UNMOUNTED); } } @@ -1314,9 +1319,9 @@ class MountService extends IMountService.Stub // XXX: This will go away soon in favor of IMountServiceObserver mPms = (PackageManagerService) ServiceManager.getService("package"); - mHandlerThread = new HandlerThread("MountService"); - mHandlerThread.start(); - mHandler = new MountServiceHandler(mHandlerThread.getLooper()); + HandlerThread hthread = new HandlerThread(TAG); + hthread.start(); + mHandler = new MountServiceHandler(hthread.getLooper()); // Watch for user changes final IntentFilter userFilter = new IntentFilter(); @@ -1338,7 +1343,7 @@ class MountService extends IMountService.Stub idleMaintenanceFilter, null, mHandler); // Add OBB Action Handler to MountService thread. - mObbActionHandler = new ObbActionHandler(mHandlerThread.getLooper()); + mObbActionHandler = new ObbActionHandler(IoThread.get().getLooper()); /* * Create the connection to vold with a maximum queue of twice the @@ -2127,6 +2132,89 @@ class MountService extends IMountService.Stub } @Override + public int mkdirs(String callingPkg, String appPath) { + final int userId = UserHandle.getUserId(Binder.getCallingUid()); + final UserEnvironment userEnv = new UserEnvironment(userId); + + // Validate that reported package name belongs to caller + final AppOpsManager appOps = (AppOpsManager) mContext.getSystemService( + Context.APP_OPS_SERVICE); + appOps.checkPackage(Binder.getCallingUid(), callingPkg); + + try { + appPath = new File(appPath).getCanonicalPath(); + } catch (IOException e) { + Slog.e(TAG, "Failed to resolve " + appPath + ": " + e); + return -1; + } + + if (!appPath.endsWith("/")) { + appPath = appPath + "/"; + } + + // Try translating the app path into a vold path, but require that it + // belong to the calling package. + String voldPath = maybeTranslatePathForVold(appPath, + userEnv.buildExternalStorageAppDataDirs(callingPkg), + userEnv.buildExternalStorageAppDataDirsForVold(callingPkg)); + if (voldPath != null) { + try { + mConnector.execute("volume", "mkdirs", voldPath); + return 0; + } catch (NativeDaemonConnectorException e) { + return e.getCode(); + } + } + + voldPath = maybeTranslatePathForVold(appPath, + userEnv.buildExternalStorageAppObbDirs(callingPkg), + userEnv.buildExternalStorageAppObbDirsForVold(callingPkg)); + if (voldPath != null) { + try { + mConnector.execute("volume", "mkdirs", voldPath); + return 0; + } catch (NativeDaemonConnectorException e) { + return e.getCode(); + } + } + + throw new SecurityException("Invalid mkdirs path: " + appPath); + } + + /** + * Translate the given path from an app-visible path to a vold-visible path, + * but only if it's under the given whitelisted paths. + * + * @param path a canonicalized app-visible path. + * @param appPaths list of app-visible paths that are allowed. + * @param voldPaths list of vold-visible paths directly corresponding to the + * allowed app-visible paths argument. + * @return a vold-visible path representing the original path, or + * {@code null} if the given path didn't have an app-to-vold + * mapping. + */ + @VisibleForTesting + public static String maybeTranslatePathForVold( + String path, File[] appPaths, File[] voldPaths) { + if (appPaths.length != voldPaths.length) { + throw new IllegalStateException("Paths must be 1:1 mapping"); + } + + for (int i = 0; i < appPaths.length; i++) { + final String appPath = appPaths[i].getAbsolutePath() + "/"; + if (path.startsWith(appPath)) { + path = new File(voldPaths[i], path.substring(appPath.length())) + .getAbsolutePath(); + if (!path.endsWith("/")) { + path = path + "/"; + } + return path; + } + } + return null; + } + + @Override public StorageVolume[] getVolumeList() { final int callingUserId = UserHandle.getCallingUserId(); final boolean accessAll = (mContext.checkPermission( @@ -2606,6 +2694,7 @@ class MountService extends IMountService.Stub @VisibleForTesting public static String buildObbPath(final String canonicalPath, int userId, boolean forVold) { // TODO: allow caller to provide Environment for full testing + // TODO: extend to support OBB mounts on secondary external storage // Only adjust paths when storage is emulated if (!Environment.isExternalStorageEmulated()) { @@ -2618,10 +2707,10 @@ class MountService extends IMountService.Stub final UserEnvironment userEnv = new UserEnvironment(userId); // /storage/emulated/0 - final String externalPath = userEnv.getExternalStorageDirectory().toString(); + final String externalPath = userEnv.getExternalStorageDirectory().getAbsolutePath(); // /storage/emulated_legacy final String legacyExternalPath = Environment.getLegacyExternalStorageDirectory() - .toString(); + .getAbsolutePath(); if (path.startsWith(externalPath)) { path = path.substring(externalPath.length() + 1); @@ -2637,18 +2726,19 @@ class MountService extends IMountService.Stub path = path.substring(obbPath.length() + 1); if (forVold) { - return new File(Environment.getEmulatedStorageObbSource(), path).toString(); + return new File(Environment.getEmulatedStorageObbSource(), path).getAbsolutePath(); } else { final UserEnvironment ownerEnv = new UserEnvironment(UserHandle.USER_OWNER); - return new File(ownerEnv.getExternalStorageObbDirectory(), path).toString(); + return new File(ownerEnv.buildExternalStorageAndroidObbDirs()[0], path) + .getAbsolutePath(); } } // Handle normal external storage paths if (forVold) { - return new File(Environment.getEmulatedStorageSource(userId), path).toString(); + return new File(Environment.getEmulatedStorageSource(userId), path).getAbsolutePath(); } else { - return new File(userEnv.getExternalStorageDirectory(), path).toString(); + return new File(userEnv.getExternalDirsForApp()[0], path).getAbsolutePath(); } } @@ -2694,6 +2784,7 @@ class MountService extends IMountService.Stub final StorageVolume v = mVolumes.get(i); pw.print(" "); pw.println(v.toString()); + pw.println(" state=" + mVolumeStates.get(v.getPath())); } } diff --git a/services/java/com/android/server/NativeDaemonConnector.java b/services/java/com/android/server/NativeDaemonConnector.java index 0a91919e4c5f..417d6d81cdf6 100644 --- a/services/java/com/android/server/NativeDaemonConnector.java +++ b/services/java/com/android/server/NativeDaemonConnector.java @@ -18,8 +18,8 @@ package com.android.server; import android.net.LocalSocket; import android.net.LocalSocketAddress; +import android.os.Build; import android.os.Handler; -import android.os.HandlerThread; import android.os.Message; import android.os.SystemClock; import android.util.LocalLog; @@ -81,9 +81,7 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo @Override public void run() { - HandlerThread thread = new HandlerThread(TAG + ".CallbackHandler"); - thread.start(); - mCallbackHandler = new Handler(thread.getLooper(), this); + mCallbackHandler = new Handler(FgThread.get().getLooper(), this); while (true) { try { @@ -108,13 +106,24 @@ final class NativeDaemonConnector implements Runnable, Handler.Callback, Watchdo return true; } + private LocalSocketAddress determineSocketAddress() { + // If we're testing, set up a socket in a namespace that's accessible to test code. + // In order to ensure that unprivileged apps aren't able to impersonate native daemons on + // production devices, even if said native daemons ill-advisedly pick a socket name that + // starts with __test__, only allow this on debug builds. + if (mSocket.startsWith("__test__") && Build.IS_DEBUGGABLE) { + return new LocalSocketAddress(mSocket); + } else { + return new LocalSocketAddress(mSocket, LocalSocketAddress.Namespace.RESERVED); + } + } + private void listenToSocket() throws IOException { LocalSocket socket = null; try { socket = new LocalSocket(); - LocalSocketAddress address = new LocalSocketAddress(mSocket, - LocalSocketAddress.Namespace.RESERVED); + LocalSocketAddress address = determineSocketAddress(); socket.connect(address); diff --git a/services/java/com/android/server/NetworkManagementService.java b/services/java/com/android/server/NetworkManagementService.java index 3b84c7323043..92f99c2e4aec 100644 --- a/services/java/com/android/server/NetworkManagementService.java +++ b/services/java/com/android/server/NetworkManagementService.java @@ -24,13 +24,14 @@ import static android.net.NetworkStats.TAG_NONE; import static android.net.NetworkStats.UID_ALL; import static android.net.TrafficStats.UID_TETHERING; import static com.android.server.NetworkManagementService.NetdResponseCode.ClatdStatusResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.GetMarkResult; import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceGetCfgResult; import static com.android.server.NetworkManagementService.NetdResponseCode.InterfaceListResult; import static com.android.server.NetworkManagementService.NetdResponseCode.IpFwdStatusResult; import static com.android.server.NetworkManagementService.NetdResponseCode.TetherDnsFwdTgtListResult; import static com.android.server.NetworkManagementService.NetdResponseCode.TetherInterfaceListResult; import static com.android.server.NetworkManagementService.NetdResponseCode.TetherStatusResult; -import static com.android.server.NetworkManagementService.NetdResponseCode.TetheringStatsResult; +import static com.android.server.NetworkManagementService.NetdResponseCode.TetheringStatsListResult; import static com.android.server.NetworkManagementService.NetdResponseCode.TtyListResult; import static com.android.server.NetworkManagementSocketTagger.PROP_QTAGUID_ENABLED; @@ -43,18 +44,21 @@ import android.net.NetworkUtils; import android.net.RouteInfo; import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiConfiguration.KeyMgmt; +import android.os.BatteryStats; import android.os.Binder; import android.os.Handler; import android.os.INetworkManagementService; import android.os.Process; import android.os.RemoteCallbackList; import android.os.RemoteException; +import android.os.ServiceManager; import android.os.SystemClock; import android.os.SystemProperties; import android.util.Log; import android.util.Slog; import android.util.SparseBooleanArray; +import com.android.internal.app.IBatteryStats; import com.android.internal.net.NetworkStatsFactory; import com.android.internal.util.Preconditions; import com.android.server.NativeDaemonConnector.Command; @@ -91,6 +95,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub private static final String TAG = "NetworkManagementService"; private static final boolean DBG = false; private static final String NETD_TAG = "NetdConnector"; + private static final String NETD_SOCKET_NAME = "netd"; private static final String ADD = "add"; private static final String REMOVE = "remove"; @@ -113,6 +118,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub public static final int TetherInterfaceListResult = 111; public static final int TetherDnsFwdTgtListResult = 112; public static final int TtyListResult = 113; + public static final int TetheringStatsListResult = 114; public static final int TetherStatusResult = 210; public static final int IpFwdStatusResult = 211; @@ -124,10 +130,12 @@ public class NetworkManagementService extends INetworkManagementService.Stub public static final int TetheringStatsResult = 221; public static final int DnsProxyQueryResult = 222; public static final int ClatdStatusResult = 223; + public static final int GetMarkResult = 225; public static final int InterfaceChange = 600; public static final int BandwidthControl = 601; public static final int InterfaceClassActivity = 613; + public static final int InterfaceAddressChange = 614; } /** @@ -181,7 +189,7 @@ public class NetworkManagementService extends INetworkManagementService.Stub * * @param context Binder context for this service */ - private NetworkManagementService(Context context) { + private NetworkManagementService(Context context, String socket) { mContext = context; if ("simulator".equals(SystemProperties.get("ro.product.device"))) { @@ -189,15 +197,16 @@ public class NetworkManagementService extends INetworkManagementService.Stub } mConnector = new NativeDaemonConnector( - new NetdCallbackReceiver(), "netd", 10, NETD_TAG, 160); + new NetdCallbackReceiver(), socket, 10, NETD_TAG, 160); mThread = new Thread(mConnector, NETD_TAG); // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); } - public static NetworkManagementService create(Context context) throws InterruptedException { - final NetworkManagementService service = new NetworkManagementService(context); + static NetworkManagementService create(Context context, + String socket) throws InterruptedException { + final NetworkManagementService service = new NetworkManagementService(context, socket); final CountDownLatch connectedSignal = service.mConnectedSignal; if (DBG) Slog.d(TAG, "Creating NetworkManagementService"); service.mThread.start(); @@ -207,6 +216,10 @@ public class NetworkManagementService extends INetworkManagementService.Stub return service; } + public static NetworkManagementService create(Context context) throws InterruptedException { + return create(context, NETD_SOCKET_NAME); + } + public void systemReady() { prepareNativeDaemon(); if (DBG) Slog.d(TAG, "Prepared"); @@ -343,6 +356,14 @@ public class NetworkManagementService extends INetworkManagementService.Stub SystemProperties.set(PROP_QTAGUID_ENABLED, mBandwidthControlEnabled ? "1" : "0"); + if (mBandwidthControlEnabled) { + try { + IBatteryStats.Stub.asInterface(ServiceManager.getService(BatteryStats.SERVICE_NAME)) + .noteNetworkStatsEnabled(); + } catch (RemoteException e) { + } + } + // push any existing quota or UID rules synchronized (mQuotaLock) { int size = mActiveQuotas.size(); @@ -380,6 +401,36 @@ public class NetworkManagementService extends INetworkManagementService.Stub setFirewallEnabled(mFirewallEnabled || LockdownVpnTracker.isEnabled()); } + /** + * Notify our observers of a new or updated interface address. + */ + private void notifyAddressUpdated(String address, String iface, int flags, int scope) { + final int length = mObservers.beginBroadcast(); + for (int i = 0; i < length; i++) { + try { + mObservers.getBroadcastItem(i).addressUpdated(address, iface, flags, scope); + } catch (RemoteException e) { + } catch (RuntimeException e) { + } + } + mObservers.finishBroadcast(); + } + + /** + * Notify our observers of a deleted interface address. + */ + private void notifyAddressRemoved(String address, String iface, int flags, int scope) { + final int length = mObservers.beginBroadcast(); + for (int i = 0; i < length; i++) { + try { + mObservers.getBroadcastItem(i).addressRemoved(address, iface, flags, scope); + } catch (RemoteException e) { + } catch (RuntimeException e) { + } + } + mObservers.finishBroadcast(); + } + // // Netd Callback handling // @@ -462,6 +513,33 @@ public class NetworkManagementService extends INetworkManagementService.Stub notifyInterfaceClassActivity(cooked[3], isActive); return true; // break; + case NetdResponseCode.InterfaceAddressChange: + /* + * A network address change occurred + * Format: "NNN Address updated <addr> <iface> <flags> <scope>" + * "NNN Address removed <addr> <iface> <flags> <scope>" + */ + String msg = String.format("Invalid event from daemon (%s)", raw); + if (cooked.length < 6 || !cooked[1].equals("Address")) { + throw new IllegalStateException(msg); + } + + int flags; + int scope; + try { + flags = Integer.parseInt(cooked[5]); + scope = Integer.parseInt(cooked[6]); + } catch(NumberFormatException e) { + throw new IllegalStateException(msg); + } + + if (cooked[2].equals("updated")) { + notifyAddressUpdated(cooked[3], cooked[4], flags, scope); + } else { + notifyAddressRemoved(cooked[3], cooked[4], flags, scope); + } + return true; + // break; default: break; } return false; @@ -762,6 +840,18 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override + public void setMtu(String iface, int mtu) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + + final NativeDaemonEvent event; + try { + event = mConnector.execute("interface", "setmtu", iface, mtu); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override public void shutdown() { // TODO: remove from aidl if nobody calls externally mContext.enforceCallingOrSelfPermission(SHUTDOWN, TAG); @@ -990,7 +1080,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub mConnector.execute("softap", "set", wlanIface); } else { mConnector.execute("softap", "set", wlanIface, wifiConfig.SSID, - getSecurityType(wifiConfig), new SensitiveArg(wifiConfig.preSharedKey)); + "broadcast", "6", getSecurityType(wifiConfig), + new SensitiveArg(wifiConfig.preSharedKey)); } mConnector.execute("softap", "startap"); } catch (NativeDaemonConnectorException e) { @@ -1039,7 +1130,8 @@ public class NetworkManagementService extends INetworkManagementService.Stub mConnector.execute("softap", "set", wlanIface); } else { mConnector.execute("softap", "set", wlanIface, wifiConfig.SSID, - getSecurityType(wifiConfig), new SensitiveArg(wifiConfig.preSharedKey)); + "broadcast", "6", getSecurityType(wifiConfig), + new SensitiveArg(wifiConfig.preSharedKey)); } } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); @@ -1283,55 +1375,42 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override - public NetworkStats getNetworkStatsTethering(String[] ifacePairs) { + public NetworkStats getNetworkStatsTethering() { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - if (ifacePairs.length % 2 != 0) { - throw new IllegalArgumentException( - "unexpected ifacePairs; length=" + ifacePairs.length); - } - final NetworkStats stats = new NetworkStats(SystemClock.elapsedRealtime(), 1); - for (int i = 0; i < ifacePairs.length; i += 2) { - final String ifaceIn = ifacePairs[i]; - final String ifaceOut = ifacePairs[i + 1]; - if (ifaceIn != null && ifaceOut != null) { - stats.combineValues(getNetworkStatsTethering(ifaceIn, ifaceOut)); - } - } - return stats; - } - - private NetworkStats.Entry getNetworkStatsTethering(String ifaceIn, String ifaceOut) { - final NativeDaemonEvent event; try { - event = mConnector.execute("bandwidth", "gettetherstats", ifaceIn, ifaceOut); + final NativeDaemonEvent[] events = mConnector.executeForList( + "bandwidth", "gettetherstats"); + for (NativeDaemonEvent event : events) { + if (event.getCode() != TetheringStatsListResult) continue; + + // 114 ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets + final StringTokenizer tok = new StringTokenizer(event.getMessage()); + try { + final String ifaceIn = tok.nextToken(); + final String ifaceOut = tok.nextToken(); + + final NetworkStats.Entry entry = new NetworkStats.Entry(); + entry.iface = ifaceOut; + entry.uid = UID_TETHERING; + entry.set = SET_DEFAULT; + entry.tag = TAG_NONE; + entry.rxBytes = Long.parseLong(tok.nextToken()); + entry.rxPackets = Long.parseLong(tok.nextToken()); + entry.txBytes = Long.parseLong(tok.nextToken()); + entry.txPackets = Long.parseLong(tok.nextToken()); + stats.combineValues(entry); + } catch (NoSuchElementException e) { + throw new IllegalStateException("problem parsing tethering stats: " + event); + } catch (NumberFormatException e) { + throw new IllegalStateException("problem parsing tethering stats: " + event); + } + } } catch (NativeDaemonConnectorException e) { throw e.rethrowAsParcelableException(); } - - event.checkCode(TetheringStatsResult); - - // 221 ifaceIn ifaceOut rx_bytes rx_packets tx_bytes tx_packets - final StringTokenizer tok = new StringTokenizer(event.getMessage()); - tok.nextToken(); - tok.nextToken(); - - try { - final NetworkStats.Entry entry = new NetworkStats.Entry(); - entry.iface = ifaceIn; - entry.uid = UID_TETHERING; - entry.set = SET_DEFAULT; - entry.tag = TAG_NONE; - entry.rxBytes = Long.parseLong(tok.nextToken()); - entry.rxPackets = Long.parseLong(tok.nextToken()); - entry.txBytes = Long.parseLong(tok.nextToken()); - entry.txPackets = Long.parseLong(tok.nextToken()); - return entry; - } catch (NumberFormatException e) { - throw new IllegalStateException( - "problem parsing tethering stats for " + ifaceIn + " " + ifaceOut + ": " + e); - } + return stats; } @Override @@ -1366,6 +1445,149 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override + public void setUidRangeRoute(String iface, int uid_start, int uid_end) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + try { + mConnector.execute("interface", "fwmark", + "uid", "add", iface, uid_start, uid_end); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void clearUidRangeRoute(String iface, int uid_start, int uid_end) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + try { + mConnector.execute("interface", "fwmark", + "uid", "remove", iface, uid_start, uid_end); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void setMarkedForwarding(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + try { + mConnector.execute("interface", "fwmark", "rule", "add", iface); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void clearMarkedForwarding(String iface) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + try { + mConnector.execute("interface", "fwmark", "rule", "remove", iface); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public int getMarkForUid(int uid) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + final NativeDaemonEvent event; + try { + event = mConnector.execute("interface", "fwmark", "get", "mark", uid); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + event.checkCode(GetMarkResult); + return Integer.parseInt(event.getMessage()); + } + + @Override + public int getMarkForProtect() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + final NativeDaemonEvent event; + try { + event = mConnector.execute("interface", "fwmark", "get", "protect"); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + event.checkCode(GetMarkResult); + return Integer.parseInt(event.getMessage()); + } + + @Override + public void setMarkedForwardingRoute(String iface, RouteInfo route) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + try { + LinkAddress dest = route.getDestination(); + mConnector.execute("interface", "fwmark", "route", "add", iface, + dest.getAddress().getHostAddress(), dest.getNetworkPrefixLength()); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void clearMarkedForwardingRoute(String iface, RouteInfo route) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + try { + LinkAddress dest = route.getDestination(); + mConnector.execute("interface", "fwmark", "route", "remove", iface, + dest.getAddress().getHostAddress(), dest.getNetworkPrefixLength()); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void setHostExemption(LinkAddress host) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + try { + mConnector.execute("interface", "fwmark", "exempt", "add", host); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void clearHostExemption(LinkAddress host) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + try { + mConnector.execute("interface", "fwmark", "exempt", "remove", host); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void setDnsInterfaceForUidRange(String iface, int uid_start, int uid_end) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + try { + mConnector.execute("resolver", "setifaceforuidrange", iface, uid_start, uid_end); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void clearDnsInterfaceForUidRange(int uid_start, int uid_end) { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + try { + mConnector.execute("resolver", "clearifaceforuidrange", uid_start, uid_end); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + @Override + public void clearDnsInterfaceMaps() { + mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); + try { + mConnector.execute("resolver", "clearifacemapping"); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + } + + + @Override public void flushDefaultDnsCache() { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); try { diff --git a/services/java/com/android/server/NetworkTimeUpdateService.java b/services/java/com/android/server/NetworkTimeUpdateService.java index 3bfd1909cf22..fddb54e138e2 100644 --- a/services/java/com/android/server/NetworkTimeUpdateService.java +++ b/services/java/com/android/server/NetworkTimeUpdateService.java @@ -71,7 +71,6 @@ public class NetworkTimeUpdateService { // NTP lookup is done on this thread and handler private Handler mHandler; - private HandlerThread mThread; private AlarmManager mAlarmManager; private PendingIntent mPendingPollIntent; private SettingsObserver mSettingsObserver; @@ -109,14 +108,14 @@ public class NetworkTimeUpdateService { } /** Initialize the receivers and initiate the first NTP request */ - public void systemReady() { + public void systemRunning() { registerForTelephonyIntents(); registerForAlarms(); registerForConnectivityIntents(); - mThread = new HandlerThread(TAG); - mThread.start(); - mHandler = new MyHandler(mThread.getLooper()); + HandlerThread thread = new HandlerThread(TAG); + thread.start(); + mHandler = new MyHandler(thread.getLooper()); // Check the network time on the new thread mHandler.obtainMessage(EVENT_POLL_NETWORK_TIME).sendToTarget(); diff --git a/services/java/com/android/server/NotificationManagerService.java b/services/java/com/android/server/NotificationManagerService.java index 29780c06e8e5..04386759df50 100644 --- a/services/java/com/android/server/NotificationManagerService.java +++ b/services/java/com/android/server/NotificationManagerService.java @@ -75,6 +75,9 @@ import android.view.accessibility.AccessibilityEvent; import android.view.accessibility.AccessibilityManager; import android.widget.Toast; +import com.android.internal.R; + +import com.android.internal.notification.NotificationScorer; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -199,6 +202,8 @@ public class NotificationManagerService extends INotificationManager.Stub private static final String TAG_PACKAGE = "package"; private static final String ATTR_NAME = "name"; + private final ArrayList<NotificationScorer> mScorers = new ArrayList<NotificationScorer>(); + private class NotificationListenerInfo implements DeathRecipient { INotificationListener listener; ComponentName component; @@ -707,7 +712,7 @@ public class NotificationManagerService extends INotificationManager.Stub intent.setComponent(name); intent.putExtra(Intent.EXTRA_CLIENT_LABEL, - com.android.internal.R.string.notification_listener_binding_label); + R.string.notification_listener_binding_label); intent.putExtra(Intent.EXTRA_CLIENT_INTENT, PendingIntent.getActivity( mContext, 0, new Intent(Settings.ACTION_NOTIFICATION_LISTENER_SETTINGS), 0)); @@ -1162,11 +1167,19 @@ public class NotificationManagerService extends INotificationManager.Stub } if (packageChanged) { // We cancel notifications for packages which have just been disabled - final int enabled = mContext.getPackageManager() - .getApplicationEnabledSetting(pkgName); - if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED - || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { - cancelNotifications = false; + try { + final int enabled = mContext.getPackageManager() + .getApplicationEnabledSetting(pkgName); + if (enabled == PackageManager.COMPONENT_ENABLED_STATE_ENABLED + || enabled == PackageManager.COMPONENT_ENABLED_STATE_DEFAULT) { + cancelNotifications = false; + } + } catch (IllegalArgumentException e) { + // Package doesn't exist; probably racing with uninstall. + // cancelNotifications is already true, so nothing to do here. + if (DBG) { + Slog.i(TAG, "Exception trying to look up app enabled setting", e); + } } } pkgList = new String[]{pkgName}; @@ -1297,19 +1310,19 @@ public class NotificationManagerService extends INotificationManager.Stub Resources resources = mContext.getResources(); mDefaultNotificationColor = resources.getColor( - com.android.internal.R.color.config_defaultNotificationColor); + R.color.config_defaultNotificationColor); mDefaultNotificationLedOn = resources.getInteger( - com.android.internal.R.integer.config_defaultNotificationLedOn); + R.integer.config_defaultNotificationLedOn); mDefaultNotificationLedOff = resources.getInteger( - com.android.internal.R.integer.config_defaultNotificationLedOff); + R.integer.config_defaultNotificationLedOff); mDefaultVibrationPattern = getLongArray(resources, - com.android.internal.R.array.config_defaultNotificationVibePattern, + R.array.config_defaultNotificationVibePattern, VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); mFallbackVibrationPattern = getLongArray(resources, - com.android.internal.R.array.config_notificationFallbackVibePattern, + R.array.config_notificationFallbackVibePattern, VIBRATE_PATTERN_MAXLEN, DEFAULT_VIBRATE_PATTERN); @@ -1344,6 +1357,24 @@ public class NotificationManagerService extends INotificationManager.Stub mSettingsObserver = new SettingsObserver(mHandler); mSettingsObserver.observe(); + + // spin up NotificationScorers + String[] notificationScorerNames = resources.getStringArray( + R.array.config_notificationScorers); + for (String scorerName : notificationScorerNames) { + try { + Class<?> scorerClass = mContext.getClassLoader().loadClass(scorerName); + NotificationScorer scorer = (NotificationScorer) scorerClass.newInstance(); + scorer.initialize(mContext); + mScorers.add(scorer); + } catch (ClassNotFoundException e) { + Slog.w(TAG, "Couldn't find scorer " + scorerName + ".", e); + } catch (InstantiationException e) { + Slog.w(TAG, "Couldn't instantiate scorer " + scorerName + ".", e); + } catch (IllegalAccessException e) { + Slog.w(TAG, "Problem accessing scorer " + scorerName + ".", e); + } + } } /** @@ -1476,7 +1507,7 @@ public class NotificationManagerService extends INotificationManager.Stub if (DBG) Slog.d(TAG, "Show pkg=" + record.pkg + " callback=" + record.callback); try { record.callback.show(); - scheduleTimeoutLocked(record, false); + scheduleTimeoutLocked(record); return; } catch (RemoteException e) { Slog.w(TAG, "Object died trying to show notification " + record.callback @@ -1516,11 +1547,11 @@ public class NotificationManagerService extends INotificationManager.Stub } } - private void scheduleTimeoutLocked(ToastRecord r, boolean immediate) + private void scheduleTimeoutLocked(ToastRecord r) { - Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r); - long delay = immediate ? 0 : (r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY); mHandler.removeCallbacksAndMessages(r); + Message m = Message.obtain(mHandler, MESSAGE_TIMEOUT, r); + long delay = r.duration == Toast.LENGTH_LONG ? LONG_DELAY : SHORT_DELAY; mHandler.sendMessageDelayed(m, delay); } @@ -1599,8 +1630,10 @@ public class NotificationManagerService extends INotificationManager.Stub // Not exposed via Binder; for system use only (otherwise malicious apps could spoof the // uid/pid of another application) - public void enqueueNotificationInternal(String pkg, String basePkg, int callingUid, - int callingPid, String tag, int id, Notification notification, int[] idOut, int userId) + + public void enqueueNotificationInternal(final String pkg, String basePkg, final int callingUid, + final int callingPid, final String tag, final int id, final Notification notification, + int[] idOut, int incomingUserId) { if (DBG) { Slog.v(TAG, "enqueueNotificationInternal: pkg=" + pkg + " id=" + id + " notification=" + notification); @@ -1608,8 +1641,8 @@ public class NotificationManagerService extends INotificationManager.Stub checkCallerIsSystemOrSameApp(pkg); final boolean isSystemNotification = isUidSystem(callingUid) || ("android".equals(pkg)); - userId = ActivityManager.handleIncomingUser(callingPid, - callingUid, userId, true, false, "enqueueNotification", pkg); + final int userId = ActivityManager.handleIncomingUser(callingPid, + callingUid, incomingUserId, true, false, "enqueueNotification", pkg); final UserHandle user = new UserHandle(userId); // Limit the number of notifications that any given package except the android @@ -1651,244 +1684,284 @@ public class NotificationManagerService extends INotificationManager.Stub } } - // === Scoring === + mHandler.post(new Runnable() { + @Override + public void run() { - // 0. Sanitize inputs - notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN, Notification.PRIORITY_MAX); - // Migrate notification flags to scores - if (0 != (notification.flags & Notification.FLAG_HIGH_PRIORITY)) { - if (notification.priority < Notification.PRIORITY_MAX) notification.priority = Notification.PRIORITY_MAX; - } else if (SCORE_ONGOING_HIGHER && 0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) { - if (notification.priority < Notification.PRIORITY_HIGH) notification.priority = Notification.PRIORITY_HIGH; - } + // === Scoring === - // 1. initial score: buckets of 10, around the app - int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20] + // 0. Sanitize inputs + notification.priority = clamp(notification.priority, Notification.PRIORITY_MIN, + Notification.PRIORITY_MAX); + // Migrate notification flags to scores + if (0 != (notification.flags & Notification.FLAG_HIGH_PRIORITY)) { + if (notification.priority < Notification.PRIORITY_MAX) { + notification.priority = Notification.PRIORITY_MAX; + } + } else if (SCORE_ONGOING_HIGHER && + 0 != (notification.flags & Notification.FLAG_ONGOING_EVENT)) { + if (notification.priority < Notification.PRIORITY_HIGH) { + notification.priority = Notification.PRIORITY_HIGH; + } + } - // 2. Consult external heuristics (TBD) + // 1. initial score: buckets of 10, around the app + int score = notification.priority * NOTIFICATION_PRIORITY_MULTIPLIER; //[-20..20] - // 3. Apply local rules + // 2. Consult external heuristics (TBD) - // blocked apps - if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) { - if (!isSystemNotification) { - score = JUNK_SCORE; - Slog.e(TAG, "Suppressing notification from package " + pkg + " by user request."); - } - } + // 3. Apply local rules - if (DBG) { - Slog.v(TAG, "Assigned score=" + score + " to " + notification); - } + int initialScore = score; + if (!mScorers.isEmpty()) { + if (DBG) Slog.v(TAG, "Initial score is " + score + "."); + for (NotificationScorer scorer : mScorers) { + try { + score = scorer.getScore(notification, score); + } catch (Throwable t) { + Slog.w(TAG, "Scorer threw on .getScore.", t); + } + } + if (DBG) Slog.v(TAG, "Final score is " + score + "."); + } - if (score < SCORE_DISPLAY_THRESHOLD) { - // Notification will be blocked because the score is too low. - return; - } + // add extra to indicate score modified by NotificationScorer + notification.extras.putBoolean(Notification.EXTRA_SCORE_MODIFIED, + score != initialScore); - // Should this notification make noise, vibe, or use the LED? - final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD); + // blocked apps + if (ENABLE_BLOCKED_NOTIFICATIONS && !noteNotificationOp(pkg, callingUid)) { + if (!isSystemNotification) { + score = JUNK_SCORE; + Slog.e(TAG, "Suppressing notification from package " + pkg + + " by user request."); + } + } - synchronized (mNotificationList) { - final StatusBarNotification n = new StatusBarNotification( - pkg, id, tag, callingUid, callingPid, score, notification, user); - NotificationRecord r = new NotificationRecord(n); - NotificationRecord old = null; - - int index = indexOfNotificationLocked(pkg, tag, id, userId); - if (index < 0) { - mNotificationList.add(r); - } else { - old = mNotificationList.remove(index); - mNotificationList.add(index, r); - // Make sure we don't lose the foreground service state. - if (old != null) { - notification.flags |= - old.getNotification().flags&Notification.FLAG_FOREGROUND_SERVICE; + if (DBG) { + Slog.v(TAG, "Assigned score=" + score + " to " + notification); } - } - // Ensure if this is a foreground service that the proper additional - // flags are set. - if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) { - notification.flags |= Notification.FLAG_ONGOING_EVENT - | Notification.FLAG_NO_CLEAR; - } + if (score < SCORE_DISPLAY_THRESHOLD) { + // Notification will be blocked because the score is too low. + return; + } - final int currentUser; - final long token = Binder.clearCallingIdentity(); - try { - currentUser = ActivityManager.getCurrentUser(); - } finally { - Binder.restoreCallingIdentity(token); - } + // Should this notification make noise, vibe, or use the LED? + final boolean canInterrupt = (score >= SCORE_INTERRUPTION_THRESHOLD); - if (notification.icon != 0) { - if (old != null && old.statusBarKey != null) { - r.statusBarKey = old.statusBarKey; - long identity = Binder.clearCallingIdentity(); - try { - mStatusBar.updateNotification(r.statusBarKey, n); - } - finally { - Binder.restoreCallingIdentity(identity); - } - } else { - long identity = Binder.clearCallingIdentity(); - try { - r.statusBarKey = mStatusBar.addNotification(n); - if ((n.getNotification().flags & Notification.FLAG_SHOW_LIGHTS) != 0 - && canInterrupt) { - mAttentionLight.pulse(); + synchronized (mNotificationList) { + final StatusBarNotification n = new StatusBarNotification( + pkg, id, tag, callingUid, callingPid, score, notification, user); + NotificationRecord r = new NotificationRecord(n); + NotificationRecord old = null; + + int index = indexOfNotificationLocked(pkg, tag, id, userId); + if (index < 0) { + mNotificationList.add(r); + } else { + old = mNotificationList.remove(index); + mNotificationList.add(index, r); + // Make sure we don't lose the foreground service state. + if (old != null) { + notification.flags |= + old.getNotification().flags & Notification.FLAG_FOREGROUND_SERVICE; } } - finally { - Binder.restoreCallingIdentity(identity); + + // Ensure if this is a foreground service that the proper additional + // flags are set. + if ((notification.flags&Notification.FLAG_FOREGROUND_SERVICE) != 0) { + notification.flags |= Notification.FLAG_ONGOING_EVENT + | Notification.FLAG_NO_CLEAR; } - } - // Send accessibility events only for the current user. - if (currentUser == userId) { - sendAccessibilityEvent(notification, pkg); - } - notifyPostedLocked(r); - } else { - Slog.e(TAG, "Not posting notification with icon==0: " + notification); - if (old != null && old.statusBarKey != null) { - long identity = Binder.clearCallingIdentity(); + final int currentUser; + final long token = Binder.clearCallingIdentity(); try { - mStatusBar.removeNotification(old.statusBarKey); - } - finally { - Binder.restoreCallingIdentity(identity); + currentUser = ActivityManager.getCurrentUser(); + } finally { + Binder.restoreCallingIdentity(token); } - notifyRemovedLocked(r); - } - // ATTENTION: in a future release we will bail out here - // so that we do not play sounds, show lights, etc. for invalid notifications - Slog.e(TAG, "WARNING: In a future release this will crash the app: " + n.getPackageName()); - } + if (notification.icon != 0) { + if (old != null && old.statusBarKey != null) { + r.statusBarKey = old.statusBarKey; + long identity = Binder.clearCallingIdentity(); + try { + mStatusBar.updateNotification(r.statusBarKey, n); + } + finally { + Binder.restoreCallingIdentity(identity); + } + } else { + long identity = Binder.clearCallingIdentity(); + try { + r.statusBarKey = mStatusBar.addNotification(n); + if ((n.getNotification().flags & Notification.FLAG_SHOW_LIGHTS) != 0 + && canInterrupt) { + mAttentionLight.pulse(); + } + } + finally { + Binder.restoreCallingIdentity(identity); + } + } + // Send accessibility events only for the current user. + if (currentUser == userId) { + sendAccessibilityEvent(notification, pkg); + } - // If we're not supposed to beep, vibrate, etc. then don't. - if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0) - && (!(old != null - && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 )) - && (r.getUserId() == UserHandle.USER_ALL || - (r.getUserId() == userId && r.getUserId() == currentUser)) - && canInterrupt - && mSystemReady) { + notifyPostedLocked(r); + } else { + Slog.e(TAG, "Not posting notification with icon==0: " + notification); + if (old != null && old.statusBarKey != null) { + long identity = Binder.clearCallingIdentity(); + try { + mStatusBar.removeNotification(old.statusBarKey); + } + finally { + Binder.restoreCallingIdentity(identity); + } - final AudioManager audioManager = (AudioManager) mContext - .getSystemService(Context.AUDIO_SERVICE); + notifyRemovedLocked(r); + } + // ATTENTION: in a future release we will bail out here + // so that we do not play sounds, show lights, etc. for invalid notifications + Slog.e(TAG, "WARNING: In a future release this will crash the app: " + + n.getPackageName()); + } - // sound + // If we're not supposed to beep, vibrate, etc. then don't. + if (((mDisabledNotifications & StatusBarManager.DISABLE_NOTIFICATION_ALERTS) == 0) + && (!(old != null + && (notification.flags & Notification.FLAG_ONLY_ALERT_ONCE) != 0 )) + && (r.getUserId() == UserHandle.USER_ALL || + (r.getUserId() == userId && r.getUserId() == currentUser)) + && canInterrupt + && mSystemReady) { + + final AudioManager audioManager = (AudioManager) mContext + .getSystemService(Context.AUDIO_SERVICE); + + // sound + + // should we use the default notification sound? (indicated either by + // DEFAULT_SOUND or because notification.sound is pointing at + // Settings.System.NOTIFICATION_SOUND) + final boolean useDefaultSound = + (notification.defaults & Notification.DEFAULT_SOUND) != 0 || + Settings.System.DEFAULT_NOTIFICATION_URI + .equals(notification.sound); + + Uri soundUri = null; + boolean hasValidSound = false; + + if (useDefaultSound) { + soundUri = Settings.System.DEFAULT_NOTIFICATION_URI; + + // check to see if the default notification sound is silent + ContentResolver resolver = mContext.getContentResolver(); + hasValidSound = Settings.System.getString(resolver, + Settings.System.NOTIFICATION_SOUND) != null; + } else if (notification.sound != null) { + soundUri = notification.sound; + hasValidSound = (soundUri != null); + } - // should we use the default notification sound? (indicated either by DEFAULT_SOUND - // or because notification.sound is pointing at Settings.System.NOTIFICATION_SOUND) - final boolean useDefaultSound = - (notification.defaults & Notification.DEFAULT_SOUND) != 0 - || Settings.System.DEFAULT_NOTIFICATION_URI.equals(notification.sound); - - Uri soundUri = null; - boolean hasValidSound = false; - - if (useDefaultSound) { - soundUri = Settings.System.DEFAULT_NOTIFICATION_URI; - - // check to see if the default notification sound is silent - ContentResolver resolver = mContext.getContentResolver(); - hasValidSound = Settings.System.getString(resolver, - Settings.System.NOTIFICATION_SOUND) != null; - } else if (notification.sound != null) { - soundUri = notification.sound; - hasValidSound = (soundUri != null); - } + if (hasValidSound) { + boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0; + int audioStreamType; + if (notification.audioStreamType >= 0) { + audioStreamType = notification.audioStreamType; + } else { + audioStreamType = DEFAULT_STREAM_TYPE; + } + mSoundNotification = r; + // do not play notifications if stream volume is 0 (typically because + // ringer mode is silent) or if there is a user of exclusive audio focus + if ((audioManager.getStreamVolume(audioStreamType) != 0) + && !audioManager.isAudioFocusExclusive()) { + final long identity = Binder.clearCallingIdentity(); + try { + final IRingtonePlayer player = mAudioService.getRingtonePlayer(); + if (player != null) { + player.playAsync(soundUri, user, looping, audioStreamType); + } + } catch (RemoteException e) { + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } - if (hasValidSound) { - boolean looping = (notification.flags & Notification.FLAG_INSISTENT) != 0; - int audioStreamType; - if (notification.audioStreamType >= 0) { - audioStreamType = notification.audioStreamType; - } else { - audioStreamType = DEFAULT_STREAM_TYPE; - } - mSoundNotification = r; - // do not play notifications if stream volume is 0 - // (typically because ringer mode is silent) or if speech recognition is active. - if ((audioManager.getStreamVolume(audioStreamType) != 0) - && !audioManager.isSpeechRecognitionActive()) { - final long identity = Binder.clearCallingIdentity(); - try { - final IRingtonePlayer player = mAudioService.getRingtonePlayer(); - if (player != null) { - player.playAsync(soundUri, user, looping, audioStreamType); + // vibrate + // Does the notification want to specify its own vibration? + final boolean hasCustomVibrate = notification.vibrate != null; + + // new in 4.2: if there was supposed to be a sound and we're in vibrate + // mode, and no other vibration is specified, we fall back to vibration + final boolean convertSoundToVibration = + !hasCustomVibrate + && hasValidSound + && (audioManager.getRingerMode() + == AudioManager.RINGER_MODE_VIBRATE); + + // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback. + final boolean useDefaultVibrate = + (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; + + if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate) + && !(audioManager.getRingerMode() + == AudioManager.RINGER_MODE_SILENT)) { + mVibrateNotification = r; + + if (useDefaultVibrate || convertSoundToVibration) { + // Escalate privileges so we can use the vibrator even if the + // notifying app does not have the VIBRATE permission. + long identity = Binder.clearCallingIdentity(); + try { + mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(), + useDefaultVibrate ? mDefaultVibrationPattern + : mFallbackVibrationPattern, + ((notification.flags & Notification.FLAG_INSISTENT) != 0) + ? 0: -1); + } finally { + Binder.restoreCallingIdentity(identity); + } + } else if (notification.vibrate.length > 1) { + // If you want your own vibration pattern, you need the VIBRATE + // permission + mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(), + notification.vibrate, + ((notification.flags & Notification.FLAG_INSISTENT) != 0) + ? 0: -1); } - } catch (RemoteException e) { - } finally { - Binder.restoreCallingIdentity(identity); } } - } - // vibrate - // Does the notification want to specify its own vibration? - final boolean hasCustomVibrate = notification.vibrate != null; - - // new in 4.2: if there was supposed to be a sound and we're in vibrate mode, - // and no other vibration is specified, we fall back to vibration - final boolean convertSoundToVibration = - !hasCustomVibrate - && hasValidSound - && (audioManager.getRingerMode() == AudioManager.RINGER_MODE_VIBRATE); - - // The DEFAULT_VIBRATE flag trumps any custom vibration AND the fallback. - final boolean useDefaultVibrate = - (notification.defaults & Notification.DEFAULT_VIBRATE) != 0; - - if ((useDefaultVibrate || convertSoundToVibration || hasCustomVibrate) - && !(audioManager.getRingerMode() == AudioManager.RINGER_MODE_SILENT)) { - mVibrateNotification = r; - - if (useDefaultVibrate || convertSoundToVibration) { - // Escalate privileges so we can use the vibrator even if the notifying app - // does not have the VIBRATE permission. - long identity = Binder.clearCallingIdentity(); - try { - mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(), - useDefaultVibrate ? mDefaultVibrationPattern - : mFallbackVibrationPattern, - ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1); - } finally { - Binder.restoreCallingIdentity(identity); + // light + // the most recent thing gets the light + mLights.remove(old); + if (mLedNotification == old) { + mLedNotification = null; + } + //Slog.i(TAG, "notification.lights=" + // + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) + // != 0)); + if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 + && canInterrupt) { + mLights.add(r); + updateLightsLocked(); + } else { + if (old != null + && ((old.getFlags() & Notification.FLAG_SHOW_LIGHTS) != 0)) { + updateLightsLocked(); } - } else if (notification.vibrate.length > 1) { - // If you want your own vibration pattern, you need the VIBRATE permission - mVibrator.vibrate(r.sbn.getUid(), r.sbn.getBasePkg(), notification.vibrate, - ((notification.flags & Notification.FLAG_INSISTENT) != 0) ? 0: -1); } } } - - // light - // the most recent thing gets the light - mLights.remove(old); - if (mLedNotification == old) { - mLedNotification = null; - } - //Slog.i(TAG, "notification.lights=" - // + ((old.notification.lights.flags & Notification.FLAG_SHOW_LIGHTS) != 0)); - if ((notification.flags & Notification.FLAG_SHOW_LIGHTS) != 0 - && canInterrupt) { - mLights.add(r); - updateLightsLocked(); - } else { - if (old != null - && ((old.getFlags() & Notification.FLAG_SHOW_LIGHTS) != 0)) { - updateLightsLocked(); - } - } - } + }); idOut[0] = id; } @@ -1980,29 +2053,39 @@ public class NotificationManagerService extends INotificationManager.Stub * Cancels a notification ONLY if it has all of the {@code mustHaveFlags} * and none of the {@code mustNotHaveFlags}. */ - private void cancelNotification(String pkg, String tag, int id, int mustHaveFlags, - int mustNotHaveFlags, boolean sendDelete, int userId) { - EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, tag, userId, - mustHaveFlags, mustNotHaveFlags); + private void cancelNotification(final String pkg, final String tag, final int id, + final int mustHaveFlags, final int mustNotHaveFlags, final boolean sendDelete, + final int userId) { + // In enqueueNotificationInternal notifications are added by scheduling the + // work on the worker handler. Hence, we also schedule the cancel on this + // handler to avoid a scenario where an add notification call followed by a + // remove notification call ends up in not removing the notification. + mHandler.post(new Runnable() { + @Override + public void run() { + EventLog.writeEvent(EventLogTags.NOTIFICATION_CANCEL, pkg, id, tag, userId, + mustHaveFlags, mustNotHaveFlags); + + synchronized (mNotificationList) { + int index = indexOfNotificationLocked(pkg, tag, id, userId); + if (index >= 0) { + NotificationRecord r = mNotificationList.get(index); + + if ((r.getNotification().flags & mustHaveFlags) != mustHaveFlags) { + return; + } + if ((r.getNotification().flags & mustNotHaveFlags) != 0) { + return; + } - synchronized (mNotificationList) { - int index = indexOfNotificationLocked(pkg, tag, id, userId); - if (index >= 0) { - NotificationRecord r = mNotificationList.get(index); + mNotificationList.remove(index); - if ((r.getNotification().flags & mustHaveFlags) != mustHaveFlags) { - return; - } - if ((r.getNotification().flags & mustNotHaveFlags) != 0) { - return; + cancelNotificationLocked(r, sendDelete); + updateLightsLocked(); + } } - - mNotificationList.remove(index); - - cancelNotificationLocked(r, sendDelete); - updateLightsLocked(); } - } + }); } /** diff --git a/services/java/com/android/server/NsdService.java b/services/java/com/android/server/NsdService.java index faa72a2c85cc..e0f415bfeaa4 100644 --- a/services/java/com/android/server/NsdService.java +++ b/services/java/com/android/server/NsdService.java @@ -417,7 +417,15 @@ public class NsdService extends INsdManager.Stub { int keyId = clientInfo.mClientIds.indexOfValue(id); if (keyId != -1) { clientId = clientInfo.mClientIds.keyAt(keyId); + } else { + // This can happen because of race conditions. For example, + // SERVICE_FOUND may race with STOP_SERVICE_DISCOVERY, + // and we may get in this situation. + Slog.d(TAG, "Notification for a listener that is no longer active: " + id); + handled = false; + return handled; } + switch (code) { case NativeResponseCode.SERVICE_FOUND: /* NNN uniqueId serviceName regType domain */ diff --git a/services/java/com/android/server/PreferredComponent.java b/services/java/com/android/server/PreferredComponent.java index bb2254516017..a7af252ca2ed 100644 --- a/services/java/com/android/server/PreferredComponent.java +++ b/services/java/com/android/server/PreferredComponent.java @@ -33,8 +33,16 @@ import java.io.PrintWriter; import java.util.List; public class PreferredComponent { + private static final String TAG_SET = "set"; + private static final String ATTR_ALWAYS = "always"; // boolean + private static final String ATTR_MATCH = "match"; // number + private static final String ATTR_NAME = "name"; // component name + private static final String ATTR_SET = "set"; // number + public final int mMatch; public final ComponentName mComponent; + // Whether this is to be the one that's always chosen. If false, it's the most recently chosen. + public boolean mAlways; private final String[] mSetPackages; private final String[] mSetClasses; @@ -50,10 +58,11 @@ public class PreferredComponent { } public PreferredComponent(Callbacks callbacks, int match, ComponentName[] set, - ComponentName component) { + ComponentName component, boolean always) { mCallbacks = callbacks; mMatch = match&IntentFilter.MATCH_CATEGORY_MASK; mComponent = component; + mAlways = always; mShortComponent = component.flattenToShortString(); mParseError = null; if (set != null) { @@ -71,7 +80,7 @@ public class PreferredComponent { } myPackages[i] = cn.getPackageName().intern(); myClasses[i] = cn.getClassName().intern(); - myComponents[i] = cn.flattenToShortString().intern(); + myComponents[i] = cn.flattenToShortString(); } mSetPackages = myPackages; mSetClasses = myClasses; @@ -86,15 +95,17 @@ public class PreferredComponent { public PreferredComponent(Callbacks callbacks, XmlPullParser parser) throws XmlPullParserException, IOException { mCallbacks = callbacks; - mShortComponent = parser.getAttributeValue(null, "name"); + mShortComponent = parser.getAttributeValue(null, ATTR_NAME); mComponent = ComponentName.unflattenFromString(mShortComponent); if (mComponent == null) { mParseError = "Bad activity name " + mShortComponent; } - String matchStr = parser.getAttributeValue(null, "match"); + String matchStr = parser.getAttributeValue(null, ATTR_MATCH); mMatch = matchStr != null ? Integer.parseInt(matchStr, 16) : 0; - String setCountStr = parser.getAttributeValue(null, "set"); + String setCountStr = parser.getAttributeValue(null, ATTR_SET); int setCount = setCountStr != null ? Integer.parseInt(setCountStr) : 0; + String alwaysStr = parser.getAttributeValue(null, ATTR_ALWAYS); + mAlways = alwaysStr != null ? Boolean.parseBoolean(alwaysStr) : true; String[] myPackages = setCount > 0 ? new String[setCount] : null; String[] myClasses = setCount > 0 ? new String[setCount] : null; @@ -115,8 +126,8 @@ public class PreferredComponent { String tagName = parser.getName(); //Log.i(TAG, "Parse outerDepth=" + outerDepth + " depth=" // + parser.getDepth() + " tag=" + tagName); - if (tagName.equals("set")) { - String name = parser.getAttributeValue(null, "name"); + if (tagName.equals(TAG_SET)) { + String name = parser.getAttributeValue(null, ATTR_NAME); if (name == null) { if (mParseError == null) { mParseError = "No name in set tag in preferred activity " @@ -166,16 +177,17 @@ public class PreferredComponent { public void writeToXml(XmlSerializer serializer, boolean full) throws IOException { final int NS = mSetClasses != null ? mSetClasses.length : 0; - serializer.attribute(null, "name", mShortComponent); + serializer.attribute(null, ATTR_NAME, mShortComponent); if (full) { if (mMatch != 0) { - serializer.attribute(null, "match", Integer.toHexString(mMatch)); + serializer.attribute(null, ATTR_MATCH, Integer.toHexString(mMatch)); } - serializer.attribute(null, "set", Integer.toString(NS)); + serializer.attribute(null, ATTR_ALWAYS, Boolean.toString(mAlways)); + serializer.attribute(null, ATTR_SET, Integer.toString(NS)); for (int s=0; s<NS; s++) { - serializer.startTag(null, "set"); - serializer.attribute(null, "name", mSetComponents[s]); - serializer.endTag(null, "set"); + serializer.startTag(null, TAG_SET); + serializer.attribute(null, ATTR_NAME, mSetComponents[s]); + serializer.endTag(null, TAG_SET); } } } @@ -207,9 +219,10 @@ public class PreferredComponent { out.print(prefix); out.print( Integer.toHexString(System.identityHashCode(ident))); out.print(' '); - out.print(mComponent.flattenToShortString()); - out.print(" match=0x"); - out.println( Integer.toHexString(mMatch)); + out.println(mShortComponent); + out.print(prefix); out.print(" mMatch=0x"); + out.print(Integer.toHexString(mMatch)); + out.print(" mAlways="); out.println(mAlways); if (mSetComponents != null) { out.print(prefix); out.println(" Selected from:"); for (int i=0; i<mSetComponents.length; i++) { diff --git a/services/java/com/android/server/ProcessMap.java b/services/java/com/android/server/ProcessMap.java deleted file mode 100644 index 6b264035b146..000000000000 --- a/services/java/com/android/server/ProcessMap.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (C) 2006 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server; - -import android.util.SparseArray; - -import java.util.HashMap; - -public class ProcessMap<E> { - final HashMap<String, SparseArray<E>> mMap - = new HashMap<String, SparseArray<E>>(); - - public E get(String name, int uid) { - SparseArray<E> uids = mMap.get(name); - if (uids == null) return null; - return uids.get(uid); - } - - public E put(String name, int uid, E value) { - SparseArray<E> uids = mMap.get(name); - if (uids == null) { - uids = new SparseArray<E>(2); - mMap.put(name, uids); - } - uids.put(uid, value); - return value; - } - - public void remove(String name, int uid) { - SparseArray<E> uids = mMap.get(name); - if (uids != null) { - uids.remove(uid); - if (uids.size() == 0) { - mMap.remove(name); - } - } - } - - public HashMap<String, SparseArray<E>> getMap() { - return mMap; - } -} diff --git a/services/java/com/android/server/StatusBarManagerService.java b/services/java/com/android/server/StatusBarManagerService.java index c21d8c66b0fb..f207c08c579c 100644 --- a/services/java/com/android/server/StatusBarManagerService.java +++ b/services/java/com/android/server/StatusBarManagerService.java @@ -399,6 +399,15 @@ public class StatusBarManagerService extends IStatusBarService.Stub mCurrentUserId = newUserId; } + @Override + public void setWindowState(int window, int state) { + if (mBar != null) { + try { + mBar.setWindowState(window, state); + } catch (RemoteException ex) {} + } + } + private void enforceStatusBar() { mContext.enforceCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR, "StatusBarManagerService"); diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index 9455017dedb8..0e0f15625823 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -44,6 +44,7 @@ import android.util.Log; import android.util.Slog; import android.view.WindowManager; +import com.android.internal.R; import com.android.internal.os.BinderInternal; import com.android.internal.os.SamplingProfilerIntegration; import com.android.server.accessibility.AccessibilityManagerService; @@ -62,6 +63,7 @@ import com.android.server.pm.PackageManagerService; import com.android.server.pm.UserManagerService; import com.android.server.power.PowerManagerService; import com.android.server.power.ShutdownThread; +import com.android.server.print.PrintManagerService; import com.android.server.search.SearchManagerService; import com.android.server.usb.UsbService; import com.android.server.wifi.WifiService; @@ -74,7 +76,7 @@ import java.io.File; import java.util.Timer; import java.util.TimerTask; -class ServerThread extends Thread { +class ServerThread { private static final String TAG = "SystemServer"; private static final String ENCRYPTING_STATE = "trigger_restart_min_framework"; private static final String ENCRYPTED_STATE = "1"; @@ -86,8 +88,7 @@ class ServerThread extends Thread { Log.wtf(TAG, "BOOT FAILURE " + msg, e); } - @Override - public void run() { + public void initAndLoop() { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_SYSTEM_RUN, SystemClock.uptimeMillis()); @@ -153,30 +154,7 @@ class ServerThread extends Thread { CommonTimeManagementService commonTimeMgmtService = null; InputManagerService inputManager = null; TelephonyRegistry telephonyRegistry = null; - - // Create a shared handler thread for UI within the system server. - // This thread is used by at least the following components: - // - WindowManagerPolicy - // - KeyguardViewManager - // - DisplayManagerService - HandlerThread uiHandlerThread = new HandlerThread("UI"); - uiHandlerThread.start(); - Handler uiHandler = new Handler(uiHandlerThread.getLooper()); - uiHandler.post(new Runnable() { - @Override - public void run() { - //Looper.myLooper().setMessageLogging(new LogPrinter( - // Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM)); - android.os.Process.setThreadPriority( - android.os.Process.THREAD_PRIORITY_FOREGROUND); - android.os.Process.setCanSelfBackground(false); - - // For debug builds, log event loop stalls to dropbox for analysis. - if (StrictMode.conditionallyEnableDebugLogging()) { - Slog.i(TAG, "Enabled StrictMode logging for UI Looper"); - } - } - }); + ConsumerIrService consumerIr = null; // Create a handler thread just for the window manager to enjoy. HandlerThread wmHandlerThread = new HandlerThread("WindowManager"); @@ -198,8 +176,9 @@ class ServerThread extends Thread { } }); - // Critical services... + // bootstrap services boolean onlyCore = false; + boolean firstBoot = false; try { // Wait for installd to finished starting up so that it has a chance to // create critical directories such as /data/user with the appropriate @@ -214,9 +193,23 @@ class ServerThread extends Thread { Slog.i(TAG, "Activity Manager"); context = ActivityManagerService.main(factoryTest); + } catch (RuntimeException e) { + Slog.e("System", "******************************************"); + Slog.e("System", "************ Failure starting bootstrap service", e); + } + + boolean disableStorage = SystemProperties.getBoolean("config.disable_storage", false); + boolean disableMedia = SystemProperties.getBoolean("config.disable_media", false); + boolean disableBluetooth = SystemProperties.getBoolean("config.disable_bluetooth", false); + boolean disableTelephony = SystemProperties.getBoolean("config.disable_telephony", false); + boolean disableLocation = SystemProperties.getBoolean("config.disable_location", false); + boolean disableSystemUI = SystemProperties.getBoolean("config.disable_systemui", false); + boolean disableNonCoreServices = SystemProperties.getBoolean("config.disable_noncore", false); + boolean disableNetwork = SystemProperties.getBoolean("config.disable_network", false); + try { Slog.i(TAG, "Display Manager"); - display = new DisplayManagerService(context, wmHandler, uiHandler); + display = new DisplayManagerService(context, wmHandler); ServiceManager.addService(Context.DISPLAY_SERVICE, display, true); Slog.i(TAG, "Telephony Registry"); @@ -224,8 +217,7 @@ class ServerThread extends Thread { ServiceManager.addService("telephony.registry", telephonyRegistry); Slog.i(TAG, "Scheduling Policy"); - ServiceManager.addService(Context.SCHEDULING_POLICY_SERVICE, - new SchedulingPolicyService()); + ServiceManager.addService("scheduling_policy", new SchedulingPolicyService()); AttributeCache.init(context); @@ -248,7 +240,6 @@ class ServerThread extends Thread { pm = PackageManagerService.main(context, installer, factoryTest != SystemServer.FACTORY_TEST_OFF, onlyCore); - boolean firstBoot = false; try { firstBoot = pm.isFirstBoot(); } catch (RemoteException e) { @@ -267,6 +258,7 @@ class ServerThread extends Thread { // The AccountManager must come before the ContentService try { + // TODO: seems like this should be disable-able, but req'd by ContentService Slog.i(TAG, "Account Manager"); accountManager = new AccountManagerService(context); ServiceManager.addService(Context.ACCOUNT_SERVICE, accountManager); @@ -292,10 +284,15 @@ class ServerThread extends Thread { vibrator = new VibratorService(context); ServiceManager.addService("vibrator", vibrator); + Slog.i(TAG, "Consumer IR Service"); + consumerIr = new ConsumerIrService(context); + ServiceManager.addService(Context.CONSUMER_IR_SERVICE, consumerIr); + // only initialize the power service after we have started the // lights service, content providers and the battery service. power.init(context, lights, ActivityManagerService.self(), battery, - BatteryStatsService.getService(), display); + BatteryStatsService.getService(), + ActivityManagerService.self().getAppOpsService(), display); Slog.i(TAG, "Alarm Manager"); alarm = new AlarmManagerService(context); @@ -304,14 +301,14 @@ class ServerThread extends Thread { Slog.i(TAG, "Init Watchdog"); Watchdog.getInstance().init(context, battery, power, alarm, ActivityManagerService.self()); + Watchdog.getInstance().addThread(wmHandler, "WindowManager thread"); Slog.i(TAG, "Input Manager"); inputManager = new InputManagerService(context, wmHandler); Slog.i(TAG, "Window Manager"); wm = WindowManagerService.main(context, power, display, inputManager, - uiHandler, wmHandler, - factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL, + wmHandler, factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL, !firstBoot, onlyCore); ServiceManager.addService(Context.WINDOW_SERVICE, wm); ServiceManager.addService(Context.INPUT_SERVICE, inputManager); @@ -334,12 +331,13 @@ class ServerThread extends Thread { } else if (!context.getPackageManager().hasSystemFeature (PackageManager.FEATURE_BLUETOOTH)) { Slog.i(TAG, "No Bluetooth Service (Bluetooth Hardware Not Present)"); + } else if (disableBluetooth) { + Slog.i(TAG, "Bluetooth Service disabled by config"); } else { Slog.i(TAG, "Bluetooth Manager Service"); bluetooth = new BluetoothManagerService(context); ServiceManager.addService(BluetoothAdapter.BLUETOOTH_MANAGER_SERVICE, bluetooth); } - } catch (RuntimeException e) { Slog.e("System", "******************************************"); Slog.e("System", "************ Failure starting core service", e); @@ -356,23 +354,28 @@ class ServerThread extends Thread { TextServicesManagerService tsms = null; LockSettingsService lockSettings = null; DreamManagerService dreamy = null; + AssetAtlasService atlas = null; + PrintManagerService printManager = null; // Bring up services needed for UI. if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { - try { - Slog.i(TAG, "Input Method Service"); - imm = new InputMethodManagerService(context, wm); - ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm); - } catch (Throwable e) { - reportWtf("starting Input Manager Service", e); - } + //if (!disableNonCoreServices) { // TODO: View depends on these; mock them? + if (true) { + try { + Slog.i(TAG, "Input Method Service"); + imm = new InputMethodManagerService(context, wm); + ServiceManager.addService(Context.INPUT_METHOD_SERVICE, imm); + } catch (Throwable e) { + reportWtf("starting Input Manager Service", e); + } - try { - Slog.i(TAG, "Accessibility Manager"); - ServiceManager.addService(Context.ACCESSIBILITY_SERVICE, - new AccessibilityManagerService(context)); - } catch (Throwable e) { - reportWtf("starting Accessibility Manager", e); + try { + Slog.i(TAG, "Accessibility Manager"); + ServiceManager.addService(Context.ACCESSIBILITY_SERVICE, + new AccessibilityManagerService(context)); + } catch (Throwable e) { + reportWtf("starting Accessibility Manager", e); + } } } @@ -397,7 +400,8 @@ class ServerThread extends Thread { } if (factoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { - if (!"0".equals(SystemProperties.get("system_init.startmountservice"))) { + if (!disableStorage && + !"0".equals(SystemProperties.get("system_init.startmountservice"))) { try { /* * NotificationManagerService is dependant on MountService, @@ -411,116 +415,131 @@ class ServerThread extends Thread { } } - try { - Slog.i(TAG, "LockSettingsService"); - lockSettings = new LockSettingsService(context); - ServiceManager.addService("lock_settings", lockSettings); - } catch (Throwable e) { - reportWtf("starting LockSettingsService service", e); - } + if (!disableNonCoreServices) { + try { + Slog.i(TAG, "LockSettingsService"); + lockSettings = new LockSettingsService(context); + ServiceManager.addService("lock_settings", lockSettings); + } catch (Throwable e) { + reportWtf("starting LockSettingsService service", e); + } - try { - Slog.i(TAG, "Device Policy"); - devicePolicy = new DevicePolicyManagerService(context); - ServiceManager.addService(Context.DEVICE_POLICY_SERVICE, devicePolicy); - } catch (Throwable e) { - reportWtf("starting DevicePolicyService", e); + try { + Slog.i(TAG, "Device Policy"); + devicePolicy = new DevicePolicyManagerService(context); + ServiceManager.addService(Context.DEVICE_POLICY_SERVICE, devicePolicy); + } catch (Throwable e) { + reportWtf("starting DevicePolicyService", e); + } } - try { - Slog.i(TAG, "Status Bar"); - statusBar = new StatusBarManagerService(context, wm); - ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar); - } catch (Throwable e) { - reportWtf("starting StatusBarManagerService", e); + if (!disableSystemUI) { + try { + Slog.i(TAG, "Status Bar"); + statusBar = new StatusBarManagerService(context, wm); + ServiceManager.addService(Context.STATUS_BAR_SERVICE, statusBar); + } catch (Throwable e) { + reportWtf("starting StatusBarManagerService", e); + } } - try { - Slog.i(TAG, "Clipboard Service"); - ServiceManager.addService(Context.CLIPBOARD_SERVICE, - new ClipboardService(context)); - } catch (Throwable e) { - reportWtf("starting Clipboard Service", e); + if (!disableNonCoreServices) { + try { + Slog.i(TAG, "Clipboard Service"); + ServiceManager.addService(Context.CLIPBOARD_SERVICE, + new ClipboardService(context)); + } catch (Throwable e) { + reportWtf("starting Clipboard Service", e); + } } - try { - Slog.i(TAG, "NetworkManagement Service"); - networkManagement = NetworkManagementService.create(context); - ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement); - } catch (Throwable e) { - reportWtf("starting NetworkManagement Service", e); + if (!disableNetwork) { + try { + Slog.i(TAG, "NetworkManagement Service"); + networkManagement = NetworkManagementService.create(context); + ServiceManager.addService(Context.NETWORKMANAGEMENT_SERVICE, networkManagement); + } catch (Throwable e) { + reportWtf("starting NetworkManagement Service", e); + } } - try { - Slog.i(TAG, "Text Service Manager Service"); - tsms = new TextServicesManagerService(context); - ServiceManager.addService(Context.TEXT_SERVICES_MANAGER_SERVICE, tsms); - } catch (Throwable e) { - reportWtf("starting Text Service Manager Service", e); + if (!disableNonCoreServices) { + try { + Slog.i(TAG, "Text Service Manager Service"); + tsms = new TextServicesManagerService(context); + ServiceManager.addService(Context.TEXT_SERVICES_MANAGER_SERVICE, tsms); + } catch (Throwable e) { + reportWtf("starting Text Service Manager Service", e); + } } - try { - Slog.i(TAG, "NetworkStats Service"); - networkStats = new NetworkStatsService(context, networkManagement, alarm); - ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats); - } catch (Throwable e) { - reportWtf("starting NetworkStats Service", e); - } + if (!disableNetwork) { + try { + Slog.i(TAG, "NetworkStats Service"); + networkStats = new NetworkStatsService(context, networkManagement, alarm); + ServiceManager.addService(Context.NETWORK_STATS_SERVICE, networkStats); + } catch (Throwable e) { + reportWtf("starting NetworkStats Service", e); + } - try { - Slog.i(TAG, "NetworkPolicy Service"); - networkPolicy = new NetworkPolicyManagerService( - context, ActivityManagerService.self(), power, - networkStats, networkManagement); - ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy); - } catch (Throwable e) { - reportWtf("starting NetworkPolicy Service", e); - } + try { + Slog.i(TAG, "NetworkPolicy Service"); + networkPolicy = new NetworkPolicyManagerService( + context, ActivityManagerService.self(), power, + networkStats, networkManagement); + ServiceManager.addService(Context.NETWORK_POLICY_SERVICE, networkPolicy); + } catch (Throwable e) { + reportWtf("starting NetworkPolicy Service", e); + } - try { - Slog.i(TAG, "Wi-Fi P2pService"); - wifiP2p = new WifiP2pService(context); - ServiceManager.addService(Context.WIFI_P2P_SERVICE, wifiP2p); - } catch (Throwable e) { - reportWtf("starting Wi-Fi P2pService", e); - } + try { + Slog.i(TAG, "Wi-Fi P2pService"); + wifiP2p = new WifiP2pService(context); + ServiceManager.addService(Context.WIFI_P2P_SERVICE, wifiP2p); + } catch (Throwable e) { + reportWtf("starting Wi-Fi P2pService", e); + } - try { - Slog.i(TAG, "Wi-Fi Service"); - wifi = new WifiService(context); - ServiceManager.addService(Context.WIFI_SERVICE, wifi); - } catch (Throwable e) { - reportWtf("starting Wi-Fi Service", e); - } + try { + Slog.i(TAG, "Wi-Fi Service"); + wifi = new WifiService(context); + ServiceManager.addService(Context.WIFI_SERVICE, wifi); + } catch (Throwable e) { + reportWtf("starting Wi-Fi Service", e); + } - try { - Slog.i(TAG, "Connectivity Service"); - connectivity = new ConnectivityService( - context, networkManagement, networkStats, networkPolicy); - ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity); - networkStats.bindConnectivityManager(connectivity); - networkPolicy.bindConnectivityManager(connectivity); - wifi.checkAndStartWifi(); - wifiP2p.connectivityServiceReady(); - } catch (Throwable e) { - reportWtf("starting Connectivity Service", e); - } + try { + Slog.i(TAG, "Connectivity Service"); + connectivity = new ConnectivityService( + context, networkManagement, networkStats, networkPolicy); + ServiceManager.addService(Context.CONNECTIVITY_SERVICE, connectivity); + networkStats.bindConnectivityManager(connectivity); + networkPolicy.bindConnectivityManager(connectivity); + + wifiP2p.connectivityServiceReady(); + wifi.checkAndStartWifi(); + } catch (Throwable e) { + reportWtf("starting Connectivity Service", e); + } - try { - Slog.i(TAG, "Network Service Discovery Service"); - serviceDiscovery = NsdService.create(context); - ServiceManager.addService( - Context.NSD_SERVICE, serviceDiscovery); - } catch (Throwable e) { - reportWtf("starting Service Discovery Service", e); + try { + Slog.i(TAG, "Network Service Discovery Service"); + serviceDiscovery = NsdService.create(context); + ServiceManager.addService( + Context.NSD_SERVICE, serviceDiscovery); + } catch (Throwable e) { + reportWtf("starting Service Discovery Service", e); + } } - try { - Slog.i(TAG, "UpdateLock Service"); - ServiceManager.addService(Context.UPDATE_LOCK_SERVICE, - new UpdateLockService(context)); - } catch (Throwable e) { - reportWtf("starting UpdateLockService", e); + if (!disableNonCoreServices) { + try { + Slog.i(TAG, "UpdateLock Service"); + ServiceManager.addService(Context.UPDATE_LOCK_SERVICE, + new UpdateLockService(context)); + } catch (Throwable e) { + reportWtf("starting UpdateLockService", e); + } } /* @@ -528,7 +547,7 @@ class ServerThread extends Thread { * AppWidget Provider. Make sure MountService is completely started * first before continuing. */ - if (mountService != null) { + if (mountService != null && !onlyCore) { mountService.waitForAsecScan(); } @@ -563,28 +582,32 @@ class ServerThread extends Thread { reportWtf("starting DeviceStorageMonitor service", e); } - try { - Slog.i(TAG, "Location Manager"); - location = new LocationManagerService(context); - ServiceManager.addService(Context.LOCATION_SERVICE, location); - } catch (Throwable e) { - reportWtf("starting Location Manager", e); - } + if (!disableLocation) { + try { + Slog.i(TAG, "Location Manager"); + location = new LocationManagerService(context); + ServiceManager.addService(Context.LOCATION_SERVICE, location); + } catch (Throwable e) { + reportWtf("starting Location Manager", e); + } - try { - Slog.i(TAG, "Country Detector"); - countryDetector = new CountryDetectorService(context); - ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector); - } catch (Throwable e) { - reportWtf("starting Country Detector", e); + try { + Slog.i(TAG, "Country Detector"); + countryDetector = new CountryDetectorService(context); + ServiceManager.addService(Context.COUNTRY_DETECTOR, countryDetector); + } catch (Throwable e) { + reportWtf("starting Country Detector", e); + } } - try { - Slog.i(TAG, "Search Service"); - ServiceManager.addService(Context.SEARCH_SERVICE, - new SearchManagerService(context)); - } catch (Throwable e) { - reportWtf("starting Search Service", e); + if (!disableNonCoreServices) { + try { + Slog.i(TAG, "Search Service"); + ServiceManager.addService(Context.SEARCH_SERVICE, + new SearchManagerService(context)); + } catch (Throwable e) { + reportWtf("starting Search Service", e); + } } try { @@ -595,8 +618,8 @@ class ServerThread extends Thread { reportWtf("starting DropBoxManagerService", e); } - if (context.getResources().getBoolean( - com.android.internal.R.bool.config_enableWallpaperService)) { + if (!disableNonCoreServices && context.getResources().getBoolean( + R.bool.config_enableWallpaperService)) { try { Slog.i(TAG, "Wallpaper Service"); if (!headless) { @@ -608,7 +631,7 @@ class ServerThread extends Thread { } } - if (!"0".equals(SystemProperties.get("system_init.startaudioservice"))) { + if (!disableMedia && !"0".equals(SystemProperties.get("system_init.startaudioservice"))) { try { Slog.i(TAG, "Audio Service"); ServiceManager.addService(Context.AUDIO_SERVICE, new AudioService(context)); @@ -617,39 +640,45 @@ class ServerThread extends Thread { } } - try { - Slog.i(TAG, "Dock Observer"); - // Listen for dock station changes - dock = new DockObserver(context); - } catch (Throwable e) { - reportWtf("starting DockObserver", e); + if (!disableNonCoreServices) { + try { + Slog.i(TAG, "Dock Observer"); + // Listen for dock station changes + dock = new DockObserver(context); + } catch (Throwable e) { + reportWtf("starting DockObserver", e); + } } - try { - Slog.i(TAG, "Wired Accessory Manager"); - // Listen for wired headset changes - inputManager.setWiredAccessoryCallbacks( - new WiredAccessoryManager(context, inputManager)); - } catch (Throwable e) { - reportWtf("starting WiredAccessoryManager", e); + if (!disableMedia) { + try { + Slog.i(TAG, "Wired Accessory Manager"); + // Listen for wired headset changes + inputManager.setWiredAccessoryCallbacks( + new WiredAccessoryManager(context, inputManager)); + } catch (Throwable e) { + reportWtf("starting WiredAccessoryManager", e); + } } - try { - Slog.i(TAG, "USB Service"); - // Manage USB host and device support - usb = new UsbService(context); - ServiceManager.addService(Context.USB_SERVICE, usb); - } catch (Throwable e) { - reportWtf("starting UsbService", e); - } + if (!disableNonCoreServices) { + try { + Slog.i(TAG, "USB Service"); + // Manage USB host and device support + usb = new UsbService(context); + ServiceManager.addService(Context.USB_SERVICE, usb); + } catch (Throwable e) { + reportWtf("starting UsbService", e); + } - try { - Slog.i(TAG, "Serial Service"); - // Serial port support - serial = new SerialService(context); - ServiceManager.addService(Context.SERIAL_SERVICE, serial); - } catch (Throwable e) { - Slog.e(TAG, "Failure starting SerialService", e); + try { + Slog.i(TAG, "Serial Service"); + // Serial port support + serial = new SerialService(context); + ServiceManager.addService(Context.SERIAL_SERVICE, serial); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting SerialService", e); + } } try { @@ -667,27 +696,29 @@ class ServerThread extends Thread { reportWtf("starting UiModeManagerService", e); } - try { - Slog.i(TAG, "Backup Service"); - ServiceManager.addService(Context.BACKUP_SERVICE, - new BackupManagerService(context)); - } catch (Throwable e) { - Slog.e(TAG, "Failure starting Backup Service", e); - } + if (!disableNonCoreServices) { + try { + Slog.i(TAG, "Backup Service"); + ServiceManager.addService(Context.BACKUP_SERVICE, + new BackupManagerService(context)); + } catch (Throwable e) { + Slog.e(TAG, "Failure starting Backup Service", e); + } - try { - Slog.i(TAG, "AppWidget Service"); - appWidget = new AppWidgetService(context); - ServiceManager.addService(Context.APPWIDGET_SERVICE, appWidget); - } catch (Throwable e) { - reportWtf("starting AppWidget Service", e); - } + try { + Slog.i(TAG, "AppWidget Service"); + appWidget = new AppWidgetService(context); + ServiceManager.addService(Context.APPWIDGET_SERVICE, appWidget); + } catch (Throwable e) { + reportWtf("starting AppWidget Service", e); + } - try { - Slog.i(TAG, "Recognition Service"); - recognition = new RecognitionManagerService(context); - } catch (Throwable e) { - reportWtf("starting Recognition Service", e); + try { + Slog.i(TAG, "Recognition Service"); + recognition = new RecognitionManagerService(context); + } catch (Throwable e) { + reportWtf("starting Recognition Service", e); + } } try { @@ -709,30 +740,36 @@ class ServerThread extends Thread { reportWtf("starting SamplingProfiler Service", e); } - try { - Slog.i(TAG, "NetworkTimeUpdateService"); - networkTimeUpdater = new NetworkTimeUpdateService(context); - } catch (Throwable e) { - reportWtf("starting NetworkTimeUpdate service", e); + if (!disableNetwork) { + try { + Slog.i(TAG, "NetworkTimeUpdateService"); + networkTimeUpdater = new NetworkTimeUpdateService(context); + } catch (Throwable e) { + reportWtf("starting NetworkTimeUpdate service", e); + } } - try { - Slog.i(TAG, "CommonTimeManagementService"); - commonTimeMgmtService = new CommonTimeManagementService(context); - ServiceManager.addService("commontime_management", commonTimeMgmtService); - } catch (Throwable e) { - reportWtf("starting CommonTimeManagementService service", e); + if (!disableMedia) { + try { + Slog.i(TAG, "CommonTimeManagementService"); + commonTimeMgmtService = new CommonTimeManagementService(context); + ServiceManager.addService("commontime_management", commonTimeMgmtService); + } catch (Throwable e) { + reportWtf("starting CommonTimeManagementService service", e); + } } - try { - Slog.i(TAG, "CertBlacklister"); - CertBlacklister blacklister = new CertBlacklister(context); - } catch (Throwable e) { - reportWtf("starting CertBlacklister", e); + if (!disableNetwork) { + try { + Slog.i(TAG, "CertBlacklister"); + CertBlacklister blacklister = new CertBlacklister(context); + } catch (Throwable e) { + reportWtf("starting CertBlacklister", e); + } } - - if (context.getResources().getBoolean( - com.android.internal.R.bool.config_dreamsSupported)) { + + if (!disableNonCoreServices && + context.getResources().getBoolean(R.bool.config_dreamsSupported)) { try { Slog.i(TAG, "Dreams Service"); // Dreams (interactive idle-time views, a/k/a screen savers) @@ -743,12 +780,30 @@ class ServerThread extends Thread { } } + if (!disableNonCoreServices) { + try { + Slog.i(TAG, "Assets Atlas Service"); + atlas = new AssetAtlasService(context); + ServiceManager.addService(AssetAtlasService.ASSET_ATLAS_SERVICE, atlas); + } catch (Throwable e) { + reportWtf("starting AssetAtlasService", e); + } + } + try { Slog.i(TAG, "IdleMaintenanceService"); new IdleMaintenanceService(context, battery); } catch (Throwable e) { reportWtf("starting IdleMaintenanceService", e); } + + try { + Slog.i(TAG, "Print Service"); + printManager = new PrintManagerService(context); + ServiceManager.addService(Context.PRINT_SERVICE, printManager); + } catch (Throwable e) { + reportWtf("starting Print Service", e); + } } // Before things start rolling, be sure we have decided whether @@ -773,10 +828,12 @@ class ServerThread extends Thread { reportWtf("making Vibrator Service ready", e); } - try { - lockSettings.systemReady(); - } catch (Throwable e) { - reportWtf("making Lock Settings Service ready", e); + if (lockSettings != null) { + try { + lockSettings.systemReady(); + } catch (Throwable e) { + reportWtf("making Lock Settings Service ready", e); + } } if (devicePolicy != null) { @@ -855,8 +912,10 @@ class ServerThread extends Thread { final TextServicesManagerService textServiceManagerServiceF = tsms; final StatusBarManagerService statusBarF = statusBar; final DreamManagerService dreamyF = dreamy; + final AssetAtlasService atlasF = atlas; final InputManagerService inputManagerF = inputManager; final TelephonyRegistry telephonyRegistryF = telephonyRegistry; + final PrintManagerService printManagerF = printManager; // We now tell the activity manager it is okay to run third party // code. It will call back into us once it has gotten to the state @@ -872,7 +931,9 @@ class ServerThread extends Thread { } catch (Throwable e) { reportWtf("observing native crashes", e); } - if (!headless) startSystemUi(contextF); + if (!headless) { + startSystemUi(contextF); + } try { if (mountServiceF != null) mountServiceF.systemReady(); } catch (Throwable e) { @@ -934,60 +995,73 @@ class ServerThread extends Thread { // third party code... try { - if (appWidgetF != null) appWidgetF.systemReady(safeMode); + if (appWidgetF != null) appWidgetF.systemRunning(safeMode); + } catch (Throwable e) { + reportWtf("Notifying AppWidgetService running", e); + } + try { + if (wallpaperF != null) wallpaperF.systemRunning(); } catch (Throwable e) { - reportWtf("making App Widget Service ready", e); + reportWtf("Notifying WallpaperService running", e); } try { - if (wallpaperF != null) wallpaperF.systemReady(); + if (immF != null) immF.systemRunning(statusBarF); } catch (Throwable e) { - reportWtf("making Wallpaper Service ready", e); + reportWtf("Notifying InputMethodService running", e); } try { - if (immF != null) immF.systemReady(statusBarF); + if (locationF != null) locationF.systemRunning(); } catch (Throwable e) { - reportWtf("making Input Method Service ready", e); + reportWtf("Notifying Location Service running", e); } try { - if (locationF != null) locationF.systemReady(); + if (countryDetectorF != null) countryDetectorF.systemRunning(); } catch (Throwable e) { - reportWtf("making Location Service ready", e); + reportWtf("Notifying CountryDetectorService running", e); } try { - if (countryDetectorF != null) countryDetectorF.systemReady(); + if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemRunning(); } catch (Throwable e) { - reportWtf("making Country Detector Service ready", e); + reportWtf("Notifying NetworkTimeService running", e); } try { - if (networkTimeUpdaterF != null) networkTimeUpdaterF.systemReady(); + if (commonTimeMgmtServiceF != null) commonTimeMgmtServiceF.systemRunning(); } catch (Throwable e) { - reportWtf("making Network Time Service ready", e); + reportWtf("Notifying CommonTimeManagementService running", e); } try { - if (commonTimeMgmtServiceF != null) commonTimeMgmtServiceF.systemReady(); + if (textServiceManagerServiceF != null) + textServiceManagerServiceF.systemRunning(); } catch (Throwable e) { - reportWtf("making Common time management service ready", e); + reportWtf("Notifying TextServicesManagerService running", e); } try { - if (textServiceManagerServiceF != null) textServiceManagerServiceF.systemReady(); + if (dreamyF != null) dreamyF.systemRunning(); } catch (Throwable e) { - reportWtf("making Text Services Manager Service ready", e); + reportWtf("Notifying DreamManagerService running", e); } try { - if (dreamyF != null) dreamyF.systemReady(); + if (atlasF != null) atlasF.systemRunning(); } catch (Throwable e) { - reportWtf("making DreamManagerService ready", e); + reportWtf("Notifying AssetAtlasService running", e); } try { // TODO(BT) Pass parameter to input manager - if (inputManagerF != null) inputManagerF.systemReady(); + if (inputManagerF != null) inputManagerF.systemRunning(); + } catch (Throwable e) { + reportWtf("Notifying InputManagerService running", e); + } + + try { + if (telephonyRegistryF != null) telephonyRegistryF.systemRunning(); } catch (Throwable e) { - reportWtf("making InputManagerService ready", e); + reportWtf("Notifying TelephonyRegistry running", e); } + try { - if (telephonyRegistryF != null) telephonyRegistryF.systemReady(); + if (printManagerF != null) printManagerF.systemRuning(); } catch (Throwable e) { - reportWtf("making TelephonyRegistry ready", e); + reportWtf("Notifying PrintManagerService running", e); } } }); @@ -1025,11 +1099,9 @@ public class SystemServer { private static final long EARLIEST_SUPPORTED_TIME = 86400 * 1000; /** - * This method is called from Zygote to initialize the system. This will cause the native - * services (SurfaceFlinger, AudioFlinger, etc..) to be started. After that it will call back - * up into init2() to start the Android services. + * Called to initialize native system services. */ - native public static void init1(String[] args); + private static native void nativeInit(); public static void main(String[] args) { if (System.currentTimeMillis() < EARLIEST_SUPPORTED_TIME) { @@ -1063,13 +1135,15 @@ public class SystemServer { Environment.setUserRequired(true); System.loadLibrary("android_servers"); - init1(args); - } - public static final void init2() { Slog.i(TAG, "Entered the Android system server!"); - Thread thr = new ServerThread(); - thr.setName("android.server.ServerThread"); - thr.start(); + + // Initialize native services. + nativeInit(); + + // This used to be its own separate thread, but now it is + // just the loop we run on the main thread. + ServerThread thr = new ServerThread(); + thr.initAndLoop(); } } diff --git a/services/java/com/android/server/TelephonyRegistry.java b/services/java/com/android/server/TelephonyRegistry.java index 17260d505fdb..699d79eb0953 100644 --- a/services/java/com/android/server/TelephonyRegistry.java +++ b/services/java/com/android/server/TelephonyRegistry.java @@ -178,7 +178,7 @@ class TelephonyRegistry extends ITelephonyRegistry.Stub { mConnectedApns = new ArrayList<String>(); } - public void systemReady() { + public void systemRunning() { // Watch for interesting updates final IntentFilter filter = new IntentFilter(); filter.addAction(Intent.ACTION_USER_SWITCHED); diff --git a/services/java/com/android/server/TextServicesManagerService.java b/services/java/com/android/server/TextServicesManagerService.java index 7dd9988b93af..09647674e07f 100644 --- a/services/java/com/android/server/TextServicesManagerService.java +++ b/services/java/com/android/server/TextServicesManagerService.java @@ -76,7 +76,7 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { new HashMap<String, SpellCheckerBindGroup>(); private final TextServicesSettings mSettings; - public void systemReady() { + public void systemRunning() { if (!mSystemReady) { mSystemReady = true; } @@ -154,6 +154,8 @@ public class TextServicesManagerService extends ITextServicesManager.Stub { mContext, mSpellCheckerList, mSpellCheckerMap, mSettings); // TODO: Update for each locale SpellCheckerInfo sci = getCurrentSpellChecker(null); + // If no spell checker is enabled, just return. The user should explicitly + // enable the spell checker. if (sci == null) return; final String packageName = sci.getPackageName(); final int change = isPackageDisappearing(packageName); diff --git a/services/java/com/android/server/TwilightService.java b/services/java/com/android/server/TwilightService.java index 154de1c34103..0356faa610f5 100644 --- a/services/java/com/android/server/TwilightService.java +++ b/services/java/com/android/server/TwilightService.java @@ -520,7 +520,7 @@ public final class TwilightService { Intent updateIntent = new Intent(ACTION_UPDATE_TWILIGHT_STATE); PendingIntent pendingIntent = PendingIntent.getBroadcast(mContext, 0, updateIntent, 0); mAlarmManager.cancel(pendingIntent); - mAlarmManager.set(AlarmManager.RTC_WAKEUP, nextUpdate, pendingIntent); + mAlarmManager.setExact(AlarmManager.RTC, nextUpdate, pendingIntent); } }; diff --git a/services/java/com/android/server/UiThread.java b/services/java/com/android/server/UiThread.java new file mode 100644 index 000000000000..60d73aa6cacb --- /dev/null +++ b/services/java/com/android/server/UiThread.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.StrictMode; +import android.util.Slog; + +/** + * Shared singleton thread for showing UI. This is a foreground thread, and in + * additional should not have operations that can take more than a few ms scheduled + * on it to avoid UI jank. + */ +public final class UiThread extends HandlerThread { + private static UiThread sInstance; + private static Handler sHandler; + + private UiThread() { + super("android.ui", android.os.Process.THREAD_PRIORITY_FOREGROUND); + } + + private static void ensureThreadLocked() { + if (sInstance == null) { + sInstance = new UiThread(); + sInstance.start(); + sHandler = new Handler(sInstance.getLooper()); + sHandler.post(new Runnable() { + @Override + public void run() { + //Looper.myLooper().setMessageLogging(new LogPrinter( + // Log.VERBOSE, "WindowManagerPolicy", Log.LOG_ID_SYSTEM)); + android.os.Process.setCanSelfBackground(false); + + // For debug builds, log event loop stalls to dropbox for analysis. + if (StrictMode.conditionallyEnableDebugLogging()) { + Slog.i("UiThread", "Enabled StrictMode logging for UI thread"); + } + } + }); + } + } + + public static UiThread get() { + synchronized (UiThread.class) { + ensureThreadLocked(); + return sInstance; + } + } + + public static Handler getHandler() { + synchronized (UiThread.class) { + ensureThreadLocked(); + return sHandler; + } + } +} diff --git a/services/java/com/android/server/VibratorService.java b/services/java/com/android/server/VibratorService.java index 21d31111c38d..28eb948d4169 100644 --- a/services/java/com/android/server/VibratorService.java +++ b/services/java/com/android/server/VibratorService.java @@ -24,6 +24,7 @@ import android.content.IntentFilter; import android.content.pm.PackageManager; import android.database.ContentObserver; import android.hardware.input.InputManager; +import android.os.BatteryStats; import android.os.Handler; import android.os.IVibratorService; import android.os.PowerManager; @@ -143,7 +144,8 @@ public class VibratorService extends IVibratorService.Stub mWakeLock.setReferenceCounted(true); mAppOpsService = IAppOpsService.Stub.asInterface(ServiceManager.getService(Context.APP_OPS_SERVICE)); - mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo")); + mBatteryStatsService = IBatteryStats.Stub.asInterface(ServiceManager.getService( + BatteryStats.SERVICE_NAME)); mVibrations = new LinkedList<Vibration>(); @@ -340,7 +342,8 @@ public class VibratorService extends IVibratorService.Stub // Lock held on mVibrations private void startVibrationLocked(final Vibration vib) { try { - int mode = mAppOpsService.startOperation(AppOpsManager.OP_VIBRATE, vib.mUid, vib.mPackageName); + int mode = mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService), + AppOpsManager.OP_VIBRATE, vib.mUid, vib.mPackageName); if (mode != AppOpsManager.MODE_ALLOWED) { if (mode == AppOpsManager.MODE_ERRORED) { Slog.w(TAG, "Would be an error: vibrate from uid " + vib.mUid); @@ -364,7 +367,8 @@ public class VibratorService extends IVibratorService.Stub private void reportFinishVibrationLocked() { if (mCurrentVibration != null) { try { - mAppOpsService.finishOperation(AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid, + mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService), + AppOpsManager.OP_VIBRATE, mCurrentVibration.mUid, mCurrentVibration.mPackageName); } catch (RemoteException e) { } diff --git a/services/java/com/android/server/WallpaperManagerService.java b/services/java/com/android/server/WallpaperManagerService.java index 6823f1363fc8..6957bac0c1eb 100644 --- a/services/java/com/android/server/WallpaperManagerService.java +++ b/services/java/com/android/server/WallpaperManagerService.java @@ -219,6 +219,8 @@ class WallpaperManagerService extends IWallpaperManager.Stub { WallpaperData mWallpaper; IRemoteCallback mReply; + boolean mDimensionsChanged = false; + public WallpaperConnection(WallpaperInfo info, WallpaperData wallpaper) { mInfo = info; mWallpaper = wallpaper; @@ -262,6 +264,14 @@ class WallpaperManagerService extends IWallpaperManager.Stub { public void attachEngine(IWallpaperEngine engine) { synchronized (mLock) { mEngine = engine; + if (mDimensionsChanged) { + try { + mEngine.setDesiredSize(mWallpaper.width, mWallpaper.height); + } catch (RemoteException e) { + Slog.w(TAG, "Failed to set wallpaper dimensions", e); + } + mDimensionsChanged = false; + } } } @@ -449,7 +459,7 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } } - public void systemReady() { + public void systemRunning() { if (DEBUG) Slog.v(TAG, "systemReady"); WallpaperData wallpaper = mWallpaperMap.get(UserHandle.USER_OWNER); switchWallpaper(wallpaper, null); @@ -652,6 +662,11 @@ class WallpaperManagerService extends IWallpaperManager.Stub { } catch (RemoteException e) { } notifyCallbacksLocked(wallpaper); + } else if (wallpaper.connection.mService != null) { + // We've attached to the service but the engine hasn't attached back to us + // yet. This means it will be created with the previous dimensions, so we + // need to update it to the new dimensions once it attaches. + wallpaper.connection.mDimensionsChanged = true; } } } @@ -821,6 +836,11 @@ class WallpaperManagerService extends IWallpaperManager.Stub { int serviceUserId = wallpaper.userId; ServiceInfo si = mIPackageManager.getServiceInfo(componentName, PackageManager.GET_META_DATA | PackageManager.GET_PERMISSIONS, serviceUserId); + if (si == null) { + // The wallpaper component we're trying to use doesn't exist + Slog.w(TAG, "Attempted wallpaper " + componentName + " is unavailable"); + return false; + } if (!android.Manifest.permission.BIND_WALLPAPER.equals(si.permission)) { String msg = "Selected service does not require " + android.Manifest.permission.BIND_WALLPAPER @@ -885,7 +905,8 @@ class WallpaperManagerService extends IWallpaperManager.Stub { Intent.createChooser(new Intent(Intent.ACTION_SET_WALLPAPER), mContext.getText(com.android.internal.R.string.chooser_wallpaper)), 0, null, new UserHandle(serviceUserId))); - if (!mContext.bindServiceAsUser(intent, newConn, Context.BIND_AUTO_CREATE, + if (!mContext.bindServiceAsUser(intent, newConn, + Context.BIND_AUTO_CREATE | Context.BIND_SHOWING_UI, new UserHandle(serviceUserId))) { String msg = "Unable to bind service: " + componentName; diff --git a/services/java/com/android/server/Watchdog.java b/services/java/com/android/server/Watchdog.java index 3aec4ea303c1..616090ebe13b 100644 --- a/services/java/com/android/server/Watchdog.java +++ b/services/java/com/android/server/Watchdog.java @@ -33,7 +33,6 @@ import android.os.BatteryManager; import android.os.Debug; import android.os.Handler; import android.os.Looper; -import android.os.Message; import android.os.Process; import android.os.ServiceManager; import android.os.SystemClock; @@ -59,20 +58,7 @@ public class Watchdog extends Thread { // Set this to true to have the watchdog record kernel thread stacks when it fires static final boolean RECORD_KERNEL_THREADS = true; - static final int MONITOR = 2718; - - static final int TIME_TO_RESTART = DB ? 15*1000 : 60*1000; - static final int TIME_TO_WAIT = TIME_TO_RESTART / 2; - - static final int MEMCHECK_DEFAULT_MIN_SCREEN_OFF = DB ? 1*60 : 5*60; // 5 minutes - static final int MEMCHECK_DEFAULT_MIN_ALARM = DB ? 1*60 : 3*60; // 3 minutes - static final int MEMCHECK_DEFAULT_RECHECK_INTERVAL = DB ? 1*60 : 5*60; // 5 minutes - - static final int REBOOT_DEFAULT_INTERVAL = DB ? 1 : 0; // never force reboot - static final int REBOOT_DEFAULT_START_TIME = 3*60*60; // 3:00am - static final int REBOOT_DEFAULT_WINDOW = 60*60; // within 1 hour - - static final String REBOOT_ACTION = "com.android.service.Watchdog.REBOOT"; + static final int TIME_TO_WAIT = DB ? 5*1000 : 30*1000; static final String[] NATIVE_STACKS_OF_INTEREST = new String[] { "/system/bin/mediaserver", @@ -83,100 +69,99 @@ public class Watchdog extends Thread { static Watchdog sWatchdog; /* This handler will be used to post message back onto the main thread */ - final Handler mHandler; - final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>(); + final ArrayList<HandlerChecker> mHandlerCheckers = new ArrayList<HandlerChecker>(); + final HandlerChecker mMonitorChecker; ContentResolver mResolver; BatteryService mBattery; PowerManagerService mPower; AlarmManagerService mAlarm; ActivityManagerService mActivity; - boolean mCompleted; - Monitor mCurrentMonitor; int mPhonePid; IActivityController mController; boolean mAllowRestart = true; - final Calendar mCalendar = Calendar.getInstance(); - int mMinScreenOff = MEMCHECK_DEFAULT_MIN_SCREEN_OFF; - int mMinAlarm = MEMCHECK_DEFAULT_MIN_ALARM; - boolean mNeedScheduledCheck; - PendingIntent mCheckupIntent; - PendingIntent mRebootIntent; + /** + * Used for checking status of handle threads and scheduling monitor callbacks. + */ + public final class HandlerChecker implements Runnable { + private final Handler mHandler; + private final String mName; + private final ArrayList<Monitor> mMonitors = new ArrayList<Monitor>(); + private boolean mCompleted; + private Monitor mCurrentMonitor; + + HandlerChecker(Handler handler, String name) { + mHandler = handler; + mName = name; + } - long mBootTime; - int mRebootInterval; + public void addMonitor(Monitor monitor) { + mMonitors.add(monitor); + } - boolean mReqRebootNoWait; // should wait for one interval before reboot? - int mReqRebootInterval = -1; // >= 0 if a reboot has been requested - int mReqRebootStartTime = -1; // >= 0 if a specific start time has been requested - int mReqRebootWindow = -1; // >= 0 if a specific window has been requested - int mReqMinScreenOff = -1; // >= 0 if a specific screen off time has been requested - int mReqMinNextAlarm = -1; // >= 0 if specific time to next alarm has been requested - int mReqRecheckInterval= -1; // >= 0 if a specific recheck interval has been requested + public void scheduleCheckLocked() { + if (mMonitors.size() == 0 && mHandler.getLooper().isIdling()) { + // If the target looper is or just recently was idling, then + // there is no reason to enqueue our checker on it since that + // is as good as it not being deadlocked. This avoid having + // to do a context switch to check the thread. Note that we + // only do this if mCheckReboot is false and we have no + // monitors, since those would need to be executed at this point. + mCompleted = true; + return; + } + mCompleted = false; + mCurrentMonitor = null; + mHandler.postAtFrontOfQueue(this); + } - /** - * Used for scheduling monitor callbacks and checking memory usage. - */ - final class HeartbeatHandler extends Handler { - HeartbeatHandler(Looper looper) { - super(looper); + public boolean isCompletedLocked() { + return mCompleted; } - @Override - public void handleMessage(Message msg) { - switch (msg.what) { - case MONITOR: { - // See if we should force a reboot. - int rebootInterval = mReqRebootInterval >= 0 - ? mReqRebootInterval : REBOOT_DEFAULT_INTERVAL; - if (mRebootInterval != rebootInterval) { - mRebootInterval = rebootInterval; - // We have been running long enough that a reboot can - // be considered... - checkReboot(false); - } + public Thread getThread() { + return mHandler.getLooper().getThread(); + } - final int size = mMonitors.size(); - for (int i = 0 ; i < size ; i++) { - synchronized (Watchdog.this) { - mCurrentMonitor = mMonitors.get(i); - } - mCurrentMonitor.monitor(); - } + public String getName() { + return mName; + } - synchronized (Watchdog.this) { - mCompleted = true; - mCurrentMonitor = null; - } - } break; + public String describeBlockedStateLocked() { + if (mCurrentMonitor == null) { + return "Blocked in handler on " + mName + " (" + getThread().getName() + ")"; + } else { + return "Blocked in monitor " + mCurrentMonitor.getClass().getName() + + " on " + mName + " (" + getThread().getName() + ")"; } } - } - final class RebootReceiver extends BroadcastReceiver { @Override - public void onReceive(Context c, Intent intent) { - if (localLOGV) Slog.v(TAG, "Alarm went off, checking reboot."); - checkReboot(true); + public void run() { + final int size = mMonitors.size(); + for (int i = 0 ; i < size ; i++) { + synchronized (Watchdog.this) { + mCurrentMonitor = mMonitors.get(i); + } + mCurrentMonitor.monitor(); + } + + synchronized (Watchdog.this) { + mCompleted = true; + mCurrentMonitor = null; + } } } final class RebootRequestReceiver extends BroadcastReceiver { @Override public void onReceive(Context c, Intent intent) { - mReqRebootNoWait = intent.getIntExtra("nowait", 0) != 0; - mReqRebootInterval = intent.getIntExtra("interval", -1); - mReqRebootStartTime = intent.getIntExtra("startTime", -1); - mReqRebootWindow = intent.getIntExtra("window", -1); - mReqMinScreenOff = intent.getIntExtra("minScreenOff", -1); - mReqMinNextAlarm = intent.getIntExtra("minNextAlarm", -1); - mReqRecheckInterval = intent.getIntExtra("recheckInterval", -1); - EventLog.writeEvent(EventLogTags.WATCHDOG_REQUESTED_REBOOT, - mReqRebootNoWait ? 1 : 0, mReqRebootInterval, - mReqRecheckInterval, mReqRebootStartTime, - mReqRebootWindow, mReqMinScreenOff, mReqMinNextAlarm); - checkReboot(true); + if (intent.getIntExtra("nowait", 0) != 0) { + rebootSystem("Received ACTION_REBOOT broadcast"); + return; + } + Slog.w(TAG, "Unsupported ACTION_REBOOT broadcast: " + intent); } } @@ -194,9 +179,23 @@ public class Watchdog extends Thread { private Watchdog() { super("watchdog"); - // Explicitly bind the HeartbeatHandler to run on the ServerThread, so - // that it can't get accidentally bound to another thread. - mHandler = new HeartbeatHandler(Looper.getMainLooper()); + // Initialize handler checkers for each common thread we want to check. Note + // that we are not currently checking the background thread, since it can + // potentially hold longer running operations with no guarantees about the timeliness + // of operations there. + + // The shared foreground thread is the main checker. It is where we + // will also dispatch monitor checks and do other work. + mMonitorChecker = new HandlerChecker(FgThread.getHandler(), "foreground thread"); + mHandlerCheckers.add(mMonitorChecker); + // Add checker for main thread. We only do a quick check since there + // can be UI running on the thread. + mHandlerCheckers.add(new HandlerChecker(new Handler(Looper.getMainLooper()), + "main thread")); + // Add checker for shared UI thread. + mHandlerCheckers.add(new HandlerChecker(UiThread.getHandler(), "ui thread")); + // And also check IO thread. + mHandlerCheckers.add(new HandlerChecker(IoThread.getHandler(), "i/o thread")); } public void init(Context context, BatteryService battery, @@ -208,16 +207,9 @@ public class Watchdog extends Thread { mAlarm = alarm; mActivity = activity; - context.registerReceiver(new RebootReceiver(), - new IntentFilter(REBOOT_ACTION)); - mRebootIntent = PendingIntent.getBroadcast(context, - 0, new Intent(REBOOT_ACTION), 0); - context.registerReceiver(new RebootRequestReceiver(), new IntentFilter(Intent.ACTION_REBOOT), android.Manifest.permission.REBOOT, null); - - mBootTime = System.currentTimeMillis(); } public void processStarted(String name, int pid) { @@ -243,87 +235,19 @@ public class Watchdog extends Thread { public void addMonitor(Monitor monitor) { synchronized (this) { if (isAlive()) { - throw new RuntimeException("Monitors can't be added while the Watchdog is running"); + throw new RuntimeException("Monitors can't be added once the Watchdog is running"); } - mMonitors.add(monitor); + mMonitorChecker.addMonitor(monitor); } } - void checkReboot(boolean fromAlarm) { - int rebootInterval = mReqRebootInterval >= 0 ? mReqRebootInterval - : REBOOT_DEFAULT_INTERVAL; - mRebootInterval = rebootInterval; - if (rebootInterval <= 0) { - // No reboot interval requested. - if (localLOGV) Slog.v(TAG, "No need to schedule a reboot alarm!"); - mAlarm.remove(mRebootIntent); - return; - } - - long rebootStartTime = mReqRebootStartTime >= 0 ? mReqRebootStartTime - : REBOOT_DEFAULT_START_TIME; - long rebootWindowMillis = (mReqRebootWindow >= 0 ? mReqRebootWindow - : REBOOT_DEFAULT_WINDOW) * 1000; - long recheckInterval = (mReqRecheckInterval >= 0 ? mReqRecheckInterval - : MEMCHECK_DEFAULT_RECHECK_INTERVAL) * 1000; - - retrieveBrutalityAmount(); - - long realStartTime; - long now; - + public void addThread(Handler thread, String name) { synchronized (this) { - now = System.currentTimeMillis(); - realStartTime = computeCalendarTime(mCalendar, now, - rebootStartTime); - - long rebootIntervalMillis = rebootInterval*24*60*60*1000; - if (DB || mReqRebootNoWait || - (now-mBootTime) >= (rebootIntervalMillis-rebootWindowMillis)) { - if (fromAlarm && rebootWindowMillis <= 0) { - // No reboot window -- just immediately reboot. - EventLog.writeEvent(EventLogTags.WATCHDOG_SCHEDULED_REBOOT, now, - (int)rebootIntervalMillis, (int)rebootStartTime*1000, - (int)rebootWindowMillis, ""); - rebootSystem("Checkin scheduled forced"); - return; - } - - // Are we within the reboot window? - if (now < realStartTime) { - // Schedule alarm for next check interval. - realStartTime = computeCalendarTime(mCalendar, - now, rebootStartTime); - } else if (now < (realStartTime+rebootWindowMillis)) { - String doit = shouldWeBeBrutalLocked(now); - EventLog.writeEvent(EventLogTags.WATCHDOG_SCHEDULED_REBOOT, now, - (int)rebootInterval, (int)rebootStartTime*1000, - (int)rebootWindowMillis, doit != null ? doit : ""); - if (doit == null) { - rebootSystem("Checked scheduled range"); - return; - } - - // Schedule next alarm either within the window or in the - // next interval. - if ((now+recheckInterval) >= (realStartTime+rebootWindowMillis)) { - realStartTime = computeCalendarTime(mCalendar, - now + rebootIntervalMillis, rebootStartTime); - } else { - realStartTime = now + recheckInterval; - } - } else { - // Schedule alarm for next check interval. - realStartTime = computeCalendarTime(mCalendar, - now + rebootIntervalMillis, rebootStartTime); - } + if (isAlive()) { + throw new RuntimeException("Threads can't be added once the Watchdog is running"); } + mHandlerCheckers.add(new HandlerChecker(thread, name)); } - - if (localLOGV) Slog.v(TAG, "Scheduling next reboot alarm for " - + ((realStartTime-now)/1000/60) + "m from now"); - mAlarm.remove(mRebootIntent); - mAlarm.set(AlarmManager.RTC_WAKEUP, realStartTime, mRebootIntent); } /** @@ -335,82 +259,55 @@ public class Watchdog extends Thread { pms.reboot(false, reason, false); } - /** - * Load the current Gservices settings for when - * {@link #shouldWeBeBrutalLocked} will allow the brutality to happen. - * Must not be called with the lock held. - */ - void retrieveBrutalityAmount() { - mMinScreenOff = (mReqMinScreenOff >= 0 ? mReqMinScreenOff - : MEMCHECK_DEFAULT_MIN_SCREEN_OFF) * 1000; - mMinAlarm = (mReqMinNextAlarm >= 0 ? mReqMinNextAlarm - : MEMCHECK_DEFAULT_MIN_ALARM) * 1000; - } - - /** - * Determine whether it is a good time to kill, crash, or otherwise - * plunder the current situation for the overall long-term benefit of - * the world. - * - * @param curTime The current system time. - * @return Returns null if this is a good time, else a String with the - * text of why it is not a good time. - */ - String shouldWeBeBrutalLocked(long curTime) { - if (mBattery == null || !mBattery.isPowered(BatteryManager.BATTERY_PLUGGED_ANY)) { - return "battery"; - } - - if (mMinScreenOff >= 0 && (mPower == null || - mPower.timeSinceScreenWasLastOn() < mMinScreenOff)) { - return "screen"; + private boolean haveAllCheckersCompletedLocked() { + for (int i=0; i<mHandlerCheckers.size(); i++) { + HandlerChecker hc = mHandlerCheckers.get(i); + if (!hc.isCompletedLocked()) { + return false; + } } + return true; + } - if (mMinAlarm >= 0 && (mAlarm == null || - mAlarm.timeToNextAlarm() < mMinAlarm)) { - return "alarm"; + private ArrayList<HandlerChecker> getBlockedCheckersLocked() { + ArrayList<HandlerChecker> checkers = new ArrayList<HandlerChecker>(); + for (int i=0; i<mHandlerCheckers.size(); i++) { + HandlerChecker hc = mHandlerCheckers.get(i); + if (!hc.isCompletedLocked()) { + checkers.add(hc); + } } - - return null; + return checkers; } - static long computeCalendarTime(Calendar c, long curTime, - long secondsSinceMidnight) { - - // start with now - c.setTimeInMillis(curTime); - - int val = (int)secondsSinceMidnight / (60*60); - c.set(Calendar.HOUR_OF_DAY, val); - secondsSinceMidnight -= val * (60*60); - val = (int)secondsSinceMidnight / 60; - c.set(Calendar.MINUTE, val); - c.set(Calendar.SECOND, (int)secondsSinceMidnight - (val*60)); - c.set(Calendar.MILLISECOND, 0); - - long newTime = c.getTimeInMillis(); - if (newTime < curTime) { - // The given time (in seconds since midnight) has already passed for today, so advance - // by one day (due to daylight savings, etc., the delta may differ from 24 hours). - c.add(Calendar.DAY_OF_MONTH, 1); - newTime = c.getTimeInMillis(); + private String describeCheckersLocked(ArrayList<HandlerChecker> checkers) { + StringBuilder builder = new StringBuilder(128); + for (int i=0; i<checkers.size(); i++) { + if (builder.length() > 0) { + builder.append(", "); + } + builder.append(checkers.get(i).describeBlockedStateLocked()); } - - return newTime; + return builder.toString(); } @Override public void run() { boolean waitedHalf = false; while (true) { - mCompleted = false; - mHandler.sendEmptyMessage(MONITOR); - - - final String name; + final ArrayList<HandlerChecker> blockedCheckers; + final String subject; final boolean allowRestart; synchronized (this) { long timeout = TIME_TO_WAIT; + if (!waitedHalf) { + // If we are not at the half-point of waiting, perform a + // new set of checks. Otherwise we are still waiting for a previous set. + for (int i=0; i<mHandlerCheckers.size(); i++) { + HandlerChecker hc = mHandlerCheckers.get(i); + hc.scheduleCheckLocked(); + } + } // NOTE: We use uptimeMillis() here because we do not want to increment the time we // wait while asleep. If the device is asleep then the thing that we are waiting @@ -426,7 +323,7 @@ public class Watchdog extends Thread { timeout = TIME_TO_WAIT - (SystemClock.uptimeMillis() - start); } - if (mCompleted) { + if (haveAllCheckersCompletedLocked()) { // The monitors have returned. waitedHalf = false; continue; @@ -443,15 +340,15 @@ public class Watchdog extends Thread { continue; } - name = (mCurrentMonitor != null) ? - mCurrentMonitor.getClass().getName() : "null"; + blockedCheckers = getBlockedCheckersLocked(); + subject = describeCheckersLocked(blockedCheckers); allowRestart = mAllowRestart; } // If we got here, that means that the system is most likely hung. // First collect stack traces from all threads of the system process. // Then kill this process so that the system will restart. - EventLog.writeEvent(EventLogTags.WATCHDOG, name); + EventLog.writeEvent(EventLogTags.WATCHDOG, subject); ArrayList<Integer> pids = new ArrayList<Integer>(); pids.add(Process.myPid()); @@ -487,7 +384,7 @@ public class Watchdog extends Thread { public void run() { mActivity.addErrorToDropBox( "watchdog", null, "system_server", null, null, - name, null, stack, null); + subject, null, stack, null); } }; dropboxThread.start(); @@ -504,7 +401,7 @@ public class Watchdog extends Thread { try { Binder.setDumpDisabled("Service dumps disabled due to hung system process."); // 1 = keep waiting, -1 = kill system - int res = controller.systemNotResponding(name); + int res = controller.systemNotResponding(subject); if (res >= 0) { Slog.i(TAG, "Activity controller requested to coninue to wait"); waitedHalf = false; @@ -520,7 +417,16 @@ public class Watchdog extends Thread { } else if (!allowRestart) { Slog.w(TAG, "Restart not allowed: Watchdog is *not* killing the system process"); } else { - Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + name); + Slog.w(TAG, "*** WATCHDOG KILLING SYSTEM PROCESS: " + subject); + for (int i=0; i<blockedCheckers.size(); i++) { + Slog.w(TAG, blockedCheckers.get(i).getName() + " stack trace:"); + StackTraceElement[] stackTrace + = blockedCheckers.get(i).getThread().getStackTrace(); + for (StackTraceElement element: stackTrace) { + Slog.w(TAG, " at " + element); + } + } + Slog.w(TAG, "*** GOODBYE!"); Process.killProcess(Process.myPid()); System.exit(10); } diff --git a/services/java/com/android/server/WiredAccessoryManager.java b/services/java/com/android/server/WiredAccessoryManager.java index d5c9c8fc96c9..415fcc126aae 100644 --- a/services/java/com/android/server/WiredAccessoryManager.java +++ b/services/java/com/android/server/WiredAccessoryManager.java @@ -44,6 +44,7 @@ import java.io.FileReader; import java.io.FileNotFoundException; import java.util.ArrayList; import java.util.List; +import java.util.Locale; /** * <p>WiredAccessoryManager monitors for a wired headset on the main board or dock using @@ -408,11 +409,11 @@ final class WiredAccessoryManager implements WiredAccessoryCallbacks { public String getDevName() { return mDevName; } public String getDevPath() { - return String.format("/devices/virtual/switch/%s", mDevName); + return String.format(Locale.US, "/devices/virtual/switch/%s", mDevName); } public String getSwitchStatePath() { - return String.format("/sys/class/switch/%s/state", mDevName); + return String.format(Locale.US, "/sys/class/switch/%s/state", mDevName); } public boolean checkSwitchExists() { diff --git a/services/java/com/android/server/accessibility/AccessibilityManagerService.java b/services/java/com/android/server/accessibility/AccessibilityManagerService.java index f1e4b0c74a20..ccac0d32ea7b 100644 --- a/services/java/com/android/server/accessibility/AccessibilityManagerService.java +++ b/services/java/com/android/server/accessibility/AccessibilityManagerService.java @@ -1419,6 +1419,11 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { Settings.Secure.TOUCH_EXPLORATION_ENABLED, enabled ? 1 : 0, userState.mUserId); } + try { + mWindowManagerService.setTouchExplorationEnabled(enabled); + } catch (RemoteException e) { + e.printStackTrace(); + } } private boolean canRequestAndRequestsTouchExplorationLocked(Service service) { @@ -2694,7 +2699,10 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { | AccessibilityNodeInfo.ACTION_COPY | AccessibilityNodeInfo.ACTION_PASTE | AccessibilityNodeInfo.ACTION_CUT - | AccessibilityNodeInfo.ACTION_SET_SELECTION; + | AccessibilityNodeInfo.ACTION_SET_SELECTION + | AccessibilityNodeInfo.ACTION_EXPAND + | AccessibilityNodeInfo.ACTION_COLLAPSE + | AccessibilityNodeInfo.ACTION_DISMISS; private static final int RETRIEVAL_ALLOWING_EVENT_TYPES = AccessibilityEvent.TYPE_VIEW_CLICKED @@ -2817,7 +2825,8 @@ public class AccessibilityManagerService extends IAccessibilityManager.Stub { public int resolveCallingUserIdEnforcingPermissionsLocked(int userId) { final int callingUid = Binder.getCallingUid(); - if (callingUid == Process.SYSTEM_UID + if (callingUid == 0 + || callingUid == Process.SYSTEM_UID || callingUid == Process.SHELL_UID) { return mCurrentUserId; } diff --git a/services/java/com/android/server/accessibility/ScreenMagnifier.java b/services/java/com/android/server/accessibility/ScreenMagnifier.java index 1bf2c4229893..5f12cf41c1b5 100644 --- a/services/java/com/android/server/accessibility/ScreenMagnifier.java +++ b/services/java/com/android/server/accessibility/ScreenMagnifier.java @@ -502,6 +502,7 @@ public final class ScreenMagnifier extends IMagnificationCallbacks.Stub public MagnifiedContentInteractonStateHandler(Context context) { mScaleGestureDetector = new ScaleGestureDetector(context, this); + mScaleGestureDetector.setQuickScaleEnabled(false); mGestureDetector = new GestureDetector(context, this); } diff --git a/services/java/com/android/server/accessibility/TouchExplorer.java b/services/java/com/android/server/accessibility/TouchExplorer.java index 18b46fbec4e6..a99b58a256e6 100644 --- a/services/java/com/android/server/accessibility/TouchExplorer.java +++ b/services/java/com/android/server/accessibility/TouchExplorer.java @@ -41,6 +41,7 @@ import com.android.internal.R; import java.util.ArrayList; import java.util.Arrays; +import java.util.List; /** * This class is a strategy for performing touch exploration. It @@ -52,10 +53,8 @@ import java.util.Arrays; * <li>2. One finger moving fast around performs gestures.</li> * <li>3. Two close fingers moving in the same direction perform a drag.</li> * <li>4. Multi-finger gestures are delivered to view hierarchy.</li> - * <li>5. Pointers that have not moved more than a specified distance after they - * went down are considered inactive.</li> - * <li>6. Two fingers moving in different directions are considered a multi-finger gesture.</li> - * <li>7. Double tapping clicks on the on the last touch explored location of it was in + * <li>5. Two fingers moving in different directions are considered a multi-finger gesture.</li> + * <li>7. Double tapping clicks on the on the last touch explored location if it was in * a window that does not take focus, otherwise the click is within the accessibility * focused rectangle.</li> * <li>7. Tapping and holding for a while performs a long press in a similar fashion @@ -102,9 +101,6 @@ class TouchExplorer implements EventStreamTransformation { // The timeout after which we are no longer trying to detect a gesture. private static final int EXIT_GESTURE_DETECTION_TIMEOUT = 2000; - // Temporary array for storing pointer IDs. - private final int[] mTempPointerIds = new int[MAX_POINTER_COUNT]; - // Timeout before trying to decide what the user is trying to do. private final int mDetermineUserIntentTimeout; @@ -129,11 +125,11 @@ class TouchExplorer implements EventStreamTransformation { // Handler for performing asynchronous operations. private final Handler mHandler; - // Command for delayed sending of a hover enter event. - private final SendHoverDelayed mSendHoverEnterDelayed; + // Command for delayed sending of a hover enter and move event. + private final SendHoverEnterAndMoveDelayed mSendHoverEnterAndMoveDelayed; // Command for delayed sending of a hover exit event. - private final SendHoverDelayed mSendHoverExitDelayed; + private final SendHoverExitDelayed mSendHoverExitDelayed; // Command for delayed sending of touch exploration end events. private final SendAccessibilityEventDelayed mSendTouchExplorationEndDelayed; @@ -220,7 +216,7 @@ class TouchExplorer implements EventStreamTransformation { public TouchExplorer(Context context, AccessibilityManagerService service) { mContext = context; mAms = service; - mReceivedPointerTracker = new ReceivedPointerTracker(context); + mReceivedPointerTracker = new ReceivedPointerTracker(); mInjectedPointerTracker = new InjectedPointerTracker(); mTapTimeout = ViewConfiguration.getTapTimeout(); mDetermineUserIntentTimeout = ViewConfiguration.getDoubleTapTimeout(); @@ -234,8 +230,8 @@ class TouchExplorer implements EventStreamTransformation { mGestureLibrary.setOrientationStyle(8); mGestureLibrary.setSequenceType(GestureStore.SEQUENCE_SENSITIVE); mGestureLibrary.load(); - mSendHoverEnterDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_ENTER, true); - mSendHoverExitDelayed = new SendHoverDelayed(MotionEvent.ACTION_HOVER_EXIT, false); + mSendHoverEnterAndMoveDelayed = new SendHoverEnterAndMoveDelayed(); + mSendHoverExitDelayed = new SendHoverExitDelayed(); mSendTouchExplorationEndDelayed = new SendAccessibilityEventDelayed( AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END, mDetermineUserIntentTimeout); @@ -283,12 +279,12 @@ class TouchExplorer implements EventStreamTransformation { } break; } // Remove all pending callbacks. - mSendHoverEnterDelayed.remove(); - mSendHoverExitDelayed.remove(); - mPerformLongPressDelayed.remove(); - mExitGestureDetectionModeDelayed.remove(); - mSendTouchExplorationEndDelayed.remove(); - mSendTouchInteractionEndDelayed.remove(); + mSendHoverEnterAndMoveDelayed.cancel(); + mSendHoverExitDelayed.cancel(); + mPerformLongPressDelayed.cancel(); + mExitGestureDetectionModeDelayed.cancel(); + mSendTouchExplorationEndDelayed.cancel(); + mSendTouchInteractionEndDelayed.cancel(); // Reset the pointer trackers. mReceivedPointerTracker.clear(); mInjectedPointerTracker.clear(); @@ -347,7 +343,7 @@ class TouchExplorer implements EventStreamTransformation { // last hover exit event. if (mSendTouchExplorationEndDelayed.isPending() && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) { - mSendTouchExplorationEndDelayed.remove(); + mSendTouchExplorationEndDelayed.cancel(); sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_END); } @@ -355,7 +351,7 @@ class TouchExplorer implements EventStreamTransformation { // last hover exit and the touch exploration gesture end events. if (mSendTouchInteractionEndDelayed.isPending() && eventType == AccessibilityEvent.TYPE_VIEW_HOVER_EXIT) { - mSendTouchInteractionEndDelayed.remove(); + mSendTouchInteractionEndDelayed.cancel(); sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); } @@ -390,95 +386,82 @@ class TouchExplorer implements EventStreamTransformation { private void handleMotionEventStateTouchExploring(MotionEvent event, MotionEvent rawEvent, int policyFlags) { ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; - final int activePointerCount = receivedTracker.getActivePointerCount(); mVelocityTracker.addMovement(rawEvent); mDoubleTapDetector.onMotionEvent(event, policyFlags); switch (event.getActionMasked()) { - case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_DOWN: { mAms.onTouchInteractionStart(); + // Pre-feed the motion events to the gesture detector since we // have a distance slop before getting into gesture detection // mode and not using the points within this slop significantly // decreases the quality of gesture recognition. handleMotionEventGestureDetecting(rawEvent, policyFlags); - //$FALL-THROUGH$ - case MotionEvent.ACTION_POINTER_DOWN: { - switch (activePointerCount) { - case 0: { - throw new IllegalStateException("The must always be one active pointer in" - + "touch exploring state!"); - } - case 1: { - // If we still have not notified the user for the last - // touch, we figure out what to do. If were waiting - // we resent the delayed callback and wait again. - if (mSendHoverEnterDelayed.isPending()) { - mSendHoverEnterDelayed.remove(); - mSendHoverExitDelayed.remove(); - } + sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_START); - if (mSendTouchExplorationEndDelayed.isPending()) { - mSendTouchExplorationEndDelayed.forceSendAndRemove(); - } + // If we still have not notified the user for the last + // touch, we figure out what to do. If were waiting + // we resent the delayed callback and wait again. + mSendHoverEnterAndMoveDelayed.cancel(); + mSendHoverExitDelayed.cancel(); + mPerformLongPressDelayed.cancel(); - if (mSendTouchInteractionEndDelayed.isPending()) { - mSendTouchInteractionEndDelayed.forceSendAndRemove(); - } + if (mSendTouchExplorationEndDelayed.isPending()) { + mSendTouchExplorationEndDelayed.forceSendAndRemove(); + } - // Every pointer that goes down is active until it moves or - // another one goes down. Hence, having more than one pointer - // down we have already send the interaction start event. - if (event.getPointerCount() == 1) { - sendAccessibilityEvent( - AccessibilityEvent.TYPE_TOUCH_INTERACTION_START); - } + if (mSendTouchInteractionEndDelayed.isPending()) { + mSendTouchInteractionEndDelayed.forceSendAndRemove(); + } - mPerformLongPressDelayed.remove(); - - // If we have the first tap schedule a long press and break - // since we do not want to schedule hover enter because - // the delayed callback will kick in before the long click. - // This would lead to a state transition resulting in long - // pressing the item below the double taped area which is - // not necessary where accessibility focus is. - if (mDoubleTapDetector.firstTapDetected()) { - // We got a tap now post a long press action. - mPerformLongPressDelayed.post(event, policyFlags); - break; - } - if (!mTouchExplorationInProgress) { - // Deliver hover enter with a delay to have a chance - // to detect what the user is trying to do. - final int pointerId = receivedTracker.getPrimaryActivePointerId(); - final int pointerIdBits = (1 << pointerId); - mSendHoverEnterDelayed.post(event, true, pointerIdBits, policyFlags); - } - } break; - default: { - /* do nothing - let the code for ACTION_MOVE decide what to do */ - } break; + // If we have the first tap, schedule a long press and break + // since we do not want to schedule hover enter because + // the delayed callback will kick in before the long click. + // This would lead to a state transition resulting in long + // pressing the item below the double taped area which is + // not necessary where accessibility focus is. + if (mDoubleTapDetector.firstTapDetected()) { + // We got a tap now post a long press action. + mPerformLongPressDelayed.post(event, policyFlags); + break; + } + if (!mTouchExplorationInProgress) { + if (!mSendHoverEnterAndMoveDelayed.isPending()) { + // Deliver hover enter with a delay to have a chance + // to detect what the user is trying to do. + final int pointerId = receivedTracker.getPrimaryPointerId(); + final int pointerIdBits = (1 << pointerId); + mSendHoverEnterAndMoveDelayed.post(event, true, pointerIdBits, + policyFlags); + } + // Cache the event until we discern exploration from gesturing. + mSendHoverEnterAndMoveDelayed.addEvent(event); } } break; + case MotionEvent.ACTION_POINTER_DOWN: { + /* do nothing - let the code for ACTION_MOVE decide what to do */ + } break; case MotionEvent.ACTION_MOVE: { - final int pointerId = receivedTracker.getPrimaryActivePointerId(); + final int pointerId = receivedTracker.getPrimaryPointerId(); final int pointerIndex = event.findPointerIndex(pointerId); final int pointerIdBits = (1 << pointerId); - switch (activePointerCount) { - case 0: { - /* do nothing - no active pointers so we swallow the event */ - } break; + switch (event.getPointerCount()) { case 1: { // We have not started sending events since we try to // figure out what the user is doing. - if (mSendHoverEnterDelayed.isPending()) { + if (mSendHoverEnterAndMoveDelayed.isPending()) { // Pre-feed the motion events to the gesture detector since we // have a distance slop before getting into gesture detection // mode and not using the points within this slop significantly // decreases the quality of gesture recognition. handleMotionEventGestureDetecting(rawEvent, policyFlags); + + // Cache the event until we discern exploration from gesturing. + mSendHoverEnterAndMoveDelayed.addEvent(event); + // It is *important* to use the distance traveled by the pointers // on the screen which may or may not be magnified. final float deltaX = receivedTracker.getReceivedPointerDownX(pointerId) @@ -500,9 +483,9 @@ class TouchExplorer implements EventStreamTransformation { // clear the current state and try to detect. mCurrentState = STATE_GESTURE_DETECTING; mVelocityTracker.clear(); - mSendHoverEnterDelayed.remove(); - mSendHoverExitDelayed.remove(); - mPerformLongPressDelayed.remove(); + mSendHoverEnterAndMoveDelayed.cancel(); + mSendHoverExitDelayed.cancel(); + mPerformLongPressDelayed.cancel(); mExitGestureDetectionModeDelayed.post(); // Send accessibility event to announce the start // of gesture recognition. @@ -511,9 +494,9 @@ class TouchExplorer implements EventStreamTransformation { } else { // We have just decided that the user is touch, // exploring so start sending events. - mSendHoverEnterDelayed.forceSendAndRemove(); - mSendHoverExitDelayed.remove(); - mPerformLongPressDelayed.remove(); + mSendHoverEnterAndMoveDelayed.forceSendAndRemove(); + mSendHoverExitDelayed.cancel(); + mPerformLongPressDelayed.cancel(); sendMotionEvent(event, MotionEvent.ACTION_HOVER_MOVE, pointerIdBits, policyFlags); } @@ -532,11 +515,11 @@ class TouchExplorer implements EventStreamTransformation { final double moveDelta = Math.hypot(deltaX, deltaY); // The user has moved enough for us to decide. if (moveDelta > mTouchSlop) { - mPerformLongPressDelayed.remove(); + mPerformLongPressDelayed.cancel(); } } - // The user is wither double tapping or performing long - // press so do not send move events yet. + // The user is either double tapping or performing a long + // press, so do not send move events yet. if (mDoubleTapDetector.firstTapDetected()) { break; } @@ -548,14 +531,14 @@ class TouchExplorer implements EventStreamTransformation { case 2: { // More than one pointer so the user is not touch exploring // and now we have to decide whether to delegate or drag. - if (mSendHoverEnterDelayed.isPending()) { + if (mSendHoverEnterAndMoveDelayed.isPending()) { // We have not started sending events so cancel // scheduled sending events. - mSendHoverEnterDelayed.remove(); - mSendHoverExitDelayed.remove(); - mPerformLongPressDelayed.remove(); + mSendHoverEnterAndMoveDelayed.cancel(); + mSendHoverExitDelayed.cancel(); + mPerformLongPressDelayed.cancel(); } else { - mPerformLongPressDelayed.remove(); + mPerformLongPressDelayed.cancel(); // If the user is touch exploring the second pointer may be // performing a double tap to activate an item without need // for the user to lift his exploring finger. @@ -590,21 +573,21 @@ class TouchExplorer implements EventStreamTransformation { } else { // Two pointers moving arbitrary are delegated to the view hierarchy. mCurrentState = STATE_DELEGATING; - sendDownForAllActiveNotInjectedPointers(event, policyFlags); + sendDownForAllNotInjectedPointers(event, policyFlags); } mVelocityTracker.clear(); } break; default: { // More than one pointer so the user is not touch exploring // and now we have to decide whether to delegate or drag. - if (mSendHoverEnterDelayed.isPending()) { + if (mSendHoverEnterAndMoveDelayed.isPending()) { // We have not started sending events so cancel // scheduled sending events. - mSendHoverEnterDelayed.remove(); - mSendHoverExitDelayed.remove(); - mPerformLongPressDelayed.remove(); + mSendHoverEnterAndMoveDelayed.cancel(); + mSendHoverExitDelayed.cancel(); + mPerformLongPressDelayed.cancel(); } else { - mPerformLongPressDelayed.remove(); + mPerformLongPressDelayed.cancel(); // We are sending events so send exit and gesture // end since we transition to another state. sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); @@ -612,43 +595,34 @@ class TouchExplorer implements EventStreamTransformation { // More than two pointers are delegated to the view hierarchy. mCurrentState = STATE_DELEGATING; - sendDownForAllActiveNotInjectedPointers(event, policyFlags); + sendDownForAllNotInjectedPointers(event, policyFlags); mVelocityTracker.clear(); } } } break; - case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_UP: { mAms.onTouchInteractionEnd(); // We know that we do not need the pre-fed gesture points are not // needed anymore since the last pointer just went up. mStrokeBuffer.clear(); - //$FALL-THROUGH$ - case MotionEvent.ACTION_POINTER_UP: { - final int pointerId = receivedTracker.getLastReceivedUpPointerId(); + final int pointerId = event.getPointerId(event.getActionIndex()); final int pointerIdBits = (1 << pointerId); - switch (activePointerCount) { - case 0: { - // If the pointer that went up was not active we have nothing to do. - if (!receivedTracker.wasLastReceivedUpPointerActive()) { - break; - } - mPerformLongPressDelayed.remove(); + mPerformLongPressDelayed.cancel(); + mVelocityTracker.clear(); - // If we have not delivered the enter schedule exit. - if (mSendHoverEnterDelayed.isPending()) { - mSendHoverExitDelayed.post(event, false, pointerIdBits, policyFlags); - } else { - // The user is touch exploring so we send events for end. - sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); - } + if (mSendHoverEnterAndMoveDelayed.isPending()) { + // If we have not delivered the enter schedule an exit. + mSendHoverExitDelayed.post(event, pointerIdBits, policyFlags); + } else { + // The user is touch exploring so we send events for end. + sendHoverExitAndTouchExplorationGestureEndIfNeeded(policyFlags); + } - if (!mSendTouchInteractionEndDelayed.isPending()) { - mSendTouchInteractionEndDelayed.post(); - } - } break; + if (!mSendTouchInteractionEndDelayed.isPending()) { + mSendTouchInteractionEndDelayed.post(); } - mVelocityTracker.clear(); + } break; case MotionEvent.ACTION_CANCEL: { clear(event, policyFlags); @@ -676,29 +650,19 @@ class TouchExplorer implements EventStreamTransformation { if (mDraggingPointerId != INVALID_POINTER_ID) { sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); } - sendDownForAllActiveNotInjectedPointers(event, policyFlags); + sendDownForAllNotInjectedPointers(event, policyFlags); } break; case MotionEvent.ACTION_MOVE: { - final int activePointerCount = mReceivedPointerTracker.getActivePointerCount(); - switch (activePointerCount) { + switch (event.getPointerCount()) { case 1: { // do nothing } break; case 2: { if (isDraggingGesture(event)) { - // If the dragging pointer are closer that a given distance we - // use the location of the primary one. Otherwise, we take the - // middle between the pointers. - int[] pointerIds = mTempPointerIds; - mReceivedPointerTracker.populateActivePointerIds(pointerIds); - - final int firstPtrIndex = event.findPointerIndex(pointerIds[0]); - final int secondPtrIndex = event.findPointerIndex(pointerIds[1]); - - final float firstPtrX = event.getX(firstPtrIndex); - final float firstPtrY = event.getY(firstPtrIndex); - final float secondPtrX = event.getX(secondPtrIndex); - final float secondPtrY = event.getY(secondPtrIndex); + final float firstPtrX = event.getX(0); + final float firstPtrY = event.getY(0); + final float secondPtrX = event.getX(1); + final float secondPtrY = event.getY(1); final float deltaX = firstPtrX - secondPtrX; final float deltaY = firstPtrY - secondPtrY; @@ -718,8 +682,8 @@ class TouchExplorer implements EventStreamTransformation { // Send an event to the end of the drag gesture. sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); - // Deliver all active pointers to the view hierarchy. - sendDownForAllActiveNotInjectedPointers(event, policyFlags); + // Deliver all pointers to the view hierarchy. + sendDownForAllNotInjectedPointers(event, policyFlags); } } break; default: { @@ -727,8 +691,8 @@ class TouchExplorer implements EventStreamTransformation { // Send an event to the end of the drag gesture. sendMotionEvent(event, MotionEvent.ACTION_UP, pointerIdBits, policyFlags); - // Deliver all active pointers to the view hierarchy. - sendDownForAllActiveNotInjectedPointers(event, policyFlags); + // Deliver all pointers to the view hierarchy. + sendDownForAllNotInjectedPointers(event, policyFlags); } } } break; @@ -771,37 +735,21 @@ class TouchExplorer implements EventStreamTransformation { throw new IllegalStateException("Delegating state can only be reached if " + "there is at least one pointer down!"); } - case MotionEvent.ACTION_MOVE: { - // Check whether some other pointer became active because they have moved - // a given distance and if such exist send them to the view hierarchy - final int notInjectedCount = getNotInjectedActivePointerCount( - mReceivedPointerTracker, mInjectedPointerTracker); - if (notInjectedCount > 0) { - MotionEvent prototype = MotionEvent.obtain(event); - sendDownForAllActiveNotInjectedPointers(prototype, policyFlags); - } - } break; - case MotionEvent.ACTION_UP: - // Announce the end of a new touch interaction. - sendAccessibilityEvent( - AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); - //$FALL-THROUGH$ - case MotionEvent.ACTION_POINTER_UP: { + case MotionEvent.ACTION_UP: { mAms.onTouchInteractionEnd(); + // Announce the end of a the touch interaction. + sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); mLongPressingPointerId = -1; mLongPressingPointerDeltaX = 0; mLongPressingPointerDeltaY = 0; - // No active pointers => go to initial state. - if (mReceivedPointerTracker.getActivePointerCount() == 0) { - mCurrentState = STATE_TOUCH_EXPLORING; - } + mCurrentState = STATE_TOUCH_EXPLORING; } break; case MotionEvent.ACTION_CANCEL: { clear(event, policyFlags); } break; } - // Deliver the event striping out inactive pointers. - sendMotionEventStripInactivePointers(event, policyFlags); + // Deliver the event. + sendMotionEvent(event, event.getAction(), ALL_POINTER_ID_BITS, policyFlags); } private void handleMotionEventGestureDetecting(MotionEvent event, int policyFlags) { @@ -826,12 +774,10 @@ class TouchExplorer implements EventStreamTransformation { } break; case MotionEvent.ACTION_UP: { mAms.onTouchInteractionEnd(); - // Announce the end of gesture recognition. - sendAccessibilityEvent( - AccessibilityEvent.TYPE_GESTURE_DETECTION_END); - // Announce the end of a new touch interaction. - sendAccessibilityEvent( - AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); + // Announce the end of the gesture recognition. + sendAccessibilityEvent(AccessibilityEvent.TYPE_GESTURE_DETECTION_END); + // Announce the end of a the touch interaction. + sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_INTERACTION_END); float x = event.getX(); float y = event.getY(); @@ -858,7 +804,7 @@ class TouchExplorer implements EventStreamTransformation { } mStrokeBuffer.clear(); - mExitGestureDetectionModeDelayed.remove(); + mExitGestureDetectionModeDelayed.cancel(); mCurrentState = STATE_TOUCH_EXPLORING; } break; case MotionEvent.ACTION_CANCEL: { @@ -876,6 +822,7 @@ class TouchExplorer implements EventStreamTransformation { AccessibilityManager accessibilityManager = AccessibilityManager.getInstance(mContext); if (accessibilityManager.isEnabled()) { AccessibilityEvent event = AccessibilityEvent.obtain(type); + event.setWindowId(mAms.getActiveWindowId()); accessibilityManager.sendAccessibilityEvent(event); switch (type) { case AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START: { @@ -889,40 +836,26 @@ class TouchExplorer implements EventStreamTransformation { } /** - * Sends down events to the view hierarchy for all active pointers which are + * Sends down events to the view hierarchy for all pointers which are * not already being delivered i.e. pointers that are not yet injected. * * @param prototype The prototype from which to create the injected events. * @param policyFlags The policy flags associated with the event. */ - private void sendDownForAllActiveNotInjectedPointers(MotionEvent prototype, int policyFlags) { - ReceivedPointerTracker receivedPointers = mReceivedPointerTracker; + private void sendDownForAllNotInjectedPointers(MotionEvent prototype, int policyFlags) { InjectedPointerTracker injectedPointers = mInjectedPointerTracker; + + // Inject the injected pointers. int pointerIdBits = 0; final int pointerCount = prototype.getPointerCount(); - - // Find which pointers are already injected. - for (int i = 0; i < pointerCount; i++) { - final int pointerId = prototype.getPointerId(i); - if (injectedPointers.isInjectedPointerDown(pointerId)) { - pointerIdBits |= (1 << pointerId); - } - } - - // Inject the active and not injected pointers. for (int i = 0; i < pointerCount; i++) { final int pointerId = prototype.getPointerId(i); - // Skip inactive pointers. - if (!receivedPointers.isActivePointer(pointerId)) { - continue; - } // Do not send event for already delivered pointers. - if (injectedPointers.isInjectedPointerDown(pointerId)) { - continue; + if (!injectedPointers.isInjectedPointerDown(pointerId)) { + pointerIdBits |= (1 << pointerId); + final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i); + sendMotionEvent(prototype, action, pointerIdBits, policyFlags); } - pointerIdBits |= (1 << pointerId); - final int action = computeInjectionAction(MotionEvent.ACTION_DOWN, i); - sendMotionEvent(prototype, action, pointerIdBits, policyFlags); } } @@ -959,7 +892,7 @@ class TouchExplorer implements EventStreamTransformation { } /** - * Sends up events to the view hierarchy for all active pointers which are + * Sends up events to the view hierarchy for all pointers which are * already being delivered i.e. pointers that are injected. * * @param prototype The prototype from which to create the injected events. @@ -982,58 +915,13 @@ class TouchExplorer implements EventStreamTransformation { } /** - * Sends a motion event by first stripping the inactive pointers. - * - * @param prototype The prototype from which to create the injected event. - * @param policyFlags The policy flags associated with the event. - */ - private void sendMotionEventStripInactivePointers(MotionEvent prototype, int policyFlags) { - ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; - - // All pointers active therefore we just inject the event as is. - if (prototype.getPointerCount() == receivedTracker.getActivePointerCount()) { - sendMotionEvent(prototype, prototype.getAction(), ALL_POINTER_ID_BITS, policyFlags); - return; - } - - // No active pointers and the one that just went up was not - // active, therefore we have nothing to do. - if (receivedTracker.getActivePointerCount() == 0 - && !receivedTracker.wasLastReceivedUpPointerActive()) { - return; - } - - // If the action pointer going up/down is not active we have nothing to do. - // However, for moves we keep going to report moves of active pointers. - final int actionMasked = prototype.getActionMasked(); - final int actionPointerId = prototype.getPointerId(prototype.getActionIndex()); - if (actionMasked != MotionEvent.ACTION_MOVE) { - if (!receivedTracker.isActiveOrWasLastActiveUpPointer(actionPointerId)) { - return; - } - } - - // If the pointer is active or the pointer that just went up - // was active we keep the pointer data in the event. - int pointerIdBits = 0; - final int pointerCount = prototype.getPointerCount(); - for (int pointerIndex = 0; pointerIndex < pointerCount; pointerIndex++) { - final int pointerId = prototype.getPointerId(pointerIndex); - if (receivedTracker.isActiveOrWasLastActiveUpPointer(pointerId)) { - pointerIdBits |= (1 << pointerId); - } - } - sendMotionEvent(prototype, prototype.getAction(), pointerIdBits, policyFlags); - } - - /** * Sends an up and down events. * * @param prototype The prototype from which to create the injected events. * @param policyFlags The policy flags associated with the event. */ private void sendActionDownAndUp(MotionEvent prototype, int policyFlags) { - // Tap with the pointer that last explored - we may have inactive pointers. + // Tap with the pointer that last explored. final int pointerId = prototype.getPointerId(prototype.getActionIndex()); final int pointerIdBits = (1 << pointerId); sendMotionEvent(prototype, MotionEvent.ACTION_DOWN, pointerIdBits, policyFlags); @@ -1215,9 +1103,9 @@ class TouchExplorer implements EventStreamTransformation { } // Remove pending event deliveries. - mSendHoverEnterDelayed.remove(); - mSendHoverExitDelayed.remove(); - mPerformLongPressDelayed.remove(); + mSendHoverEnterAndMoveDelayed.cancel(); + mSendHoverExitDelayed.cancel(); + mPerformLongPressDelayed.cancel(); if (mSendTouchExplorationEndDelayed.isPending()) { mSendTouchExplorationEndDelayed.forceSendAndRemove(); @@ -1307,21 +1195,16 @@ class TouchExplorer implements EventStreamTransformation { */ private boolean isDraggingGesture(MotionEvent event) { ReceivedPointerTracker receivedTracker = mReceivedPointerTracker; - int[] pointerIds = mTempPointerIds; - receivedTracker.populateActivePointerIds(pointerIds); - - final int firstPtrIndex = event.findPointerIndex(pointerIds[0]); - final int secondPtrIndex = event.findPointerIndex(pointerIds[1]); - final float firstPtrX = event.getX(firstPtrIndex); - final float firstPtrY = event.getY(firstPtrIndex); - final float secondPtrX = event.getX(secondPtrIndex); - final float secondPtrY = event.getY(secondPtrIndex); + final float firstPtrX = event.getX(0); + final float firstPtrY = event.getY(0); + final float secondPtrX = event.getX(1); + final float secondPtrY = event.getY(1); - final float firstPtrDownX = receivedTracker.getReceivedPointerDownX(firstPtrIndex); - final float firstPtrDownY = receivedTracker.getReceivedPointerDownY(firstPtrIndex); - final float secondPtrDownX = receivedTracker.getReceivedPointerDownX(secondPtrIndex); - final float secondPtrDownY = receivedTracker.getReceivedPointerDownY(secondPtrIndex); + final float firstPtrDownX = receivedTracker.getReceivedPointerDownX(0); + final float firstPtrDownY = receivedTracker.getReceivedPointerDownY(0); + final float secondPtrDownX = receivedTracker.getReceivedPointerDownX(1); + final float secondPtrDownY = receivedTracker.getReceivedPointerDownY(1); return GestureUtils.isDraggingGesture(firstPtrDownX, firstPtrDownY, secondPtrDownX, secondPtrDownY, firstPtrX, firstPtrY, secondPtrX, secondPtrY, @@ -1350,16 +1233,6 @@ class TouchExplorer implements EventStreamTransformation { } /** - * @return The number of non injected active pointers. - */ - private int getNotInjectedActivePointerCount(ReceivedPointerTracker receivedTracker, - InjectedPointerTracker injectedTracker) { - final int pointerState = receivedTracker.getActivePointers() - & ~injectedTracker.getInjectedPointersDown(); - return Integer.bitCount(pointerState); - } - - /** * Class for delayed exiting from gesture detecting mode. */ private final class ExitGestureDetectionModeDelayed implements Runnable { @@ -1368,7 +1241,7 @@ class TouchExplorer implements EventStreamTransformation { mHandler.postDelayed(this, EXIT_GESTURE_DETECTION_TIMEOUT); } - public void remove() { + public void cancel() { mHandler.removeCallbacks(this); } @@ -1396,21 +1269,21 @@ class TouchExplorer implements EventStreamTransformation { mHandler.postDelayed(this, ViewConfiguration.getLongPressTimeout()); } - public void remove() { - if (isPending()) { + public void cancel() { + if (mEvent != null) { mHandler.removeCallbacks(this); clear(); } } - public boolean isPending() { - return (mEvent != null); + private boolean isPending() { + return mHandler.hasCallbacks(this); } @Override public void run() { - // Active pointers should not be zero when running this command. - if (mReceivedPointerTracker.getActivePointerCount() == 0) { + // Pointers should not be zero when running this command. + if (mReceivedPointerTracker.getLastReceivedEvent().getPointerCount() == 0) { return; } @@ -1461,14 +1334,11 @@ class TouchExplorer implements EventStreamTransformation { sendHoverExitAndTouchExplorationGestureEndIfNeeded(mPolicyFlags); mCurrentState = STATE_DELEGATING; - sendDownForAllActiveNotInjectedPointers(mEvent, mPolicyFlags); + sendDownForAllNotInjectedPointers(mEvent, mPolicyFlags); clear(); } private void clear() { - if (!isPending()) { - return; - } mEvent.recycle(); mEvent = null; mPolicyFlags = 0; @@ -1476,59 +1346,114 @@ class TouchExplorer implements EventStreamTransformation { } /** - * Class for delayed sending of hover events. + * Class for delayed sending of hover enter and move events. */ - class SendHoverDelayed implements Runnable { - private final String LOG_TAG_SEND_HOVER_DELAYED = SendHoverDelayed.class.getName(); + class SendHoverEnterAndMoveDelayed implements Runnable { + private final String LOG_TAG_SEND_HOVER_DELAYED = "SendHoverEnterAndMoveDelayed"; - private final int mHoverAction; - private final boolean mGestureStarted; + private final List<MotionEvent> mEvents = new ArrayList<MotionEvent>(); - private MotionEvent mPrototype; private int mPointerIdBits; private int mPolicyFlags; - public SendHoverDelayed(int hoverAction, boolean gestureStarted) { - mHoverAction = hoverAction; - mGestureStarted = gestureStarted; - } - - public void post(MotionEvent prototype, boolean touchExplorationInProgress, + public void post(MotionEvent event, boolean touchExplorationInProgress, int pointerIdBits, int policyFlags) { - remove(); - mPrototype = MotionEvent.obtain(prototype); + cancel(); + addEvent(event); mPointerIdBits = pointerIdBits; mPolicyFlags = policyFlags; mHandler.postDelayed(this, mDetermineUserIntentTimeout); } - public float getX() { + public void addEvent(MotionEvent event) { + mEvents.add(MotionEvent.obtain(event)); + } + + public void cancel() { if (isPending()) { - return mPrototype.getX(); + mHandler.removeCallbacks(this); + clear(); + } + } + + private boolean isPending() { + return mHandler.hasCallbacks(this); + } + + private void clear() { + mPointerIdBits = -1; + mPolicyFlags = 0; + final int eventCount = mEvents.size(); + for (int i = eventCount - 1; i >= 0; i--) { + mEvents.remove(i).recycle(); } - return 0; } - public float getY() { + public void forceSendAndRemove() { if (isPending()) { - return mPrototype.getY(); + run(); + cancel(); } - return 0; } - public void remove() { - mHandler.removeCallbacks(this); + public void run() { + // Send an accessibility event to announce the touch exploration start. + sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); + + if (!mEvents.isEmpty()) { + // Deliver a down event. + sendMotionEvent(mEvents.get(0), MotionEvent.ACTION_HOVER_ENTER, + mPointerIdBits, mPolicyFlags); + if (DEBUG) { + Slog.d(LOG_TAG_SEND_HOVER_DELAYED, + "Injecting motion event: ACTION_HOVER_ENTER"); + } + + // Deliver move events. + final int eventCount = mEvents.size(); + for (int i = 1; i < eventCount; i++) { + sendMotionEvent(mEvents.get(i), MotionEvent.ACTION_HOVER_MOVE, + mPointerIdBits, mPolicyFlags); + if (DEBUG) { + Slog.d(LOG_TAG_SEND_HOVER_DELAYED, + "Injecting motion event: ACTION_HOVER_MOVE"); + } + } + } clear(); } + } + + /** + * Class for delayed sending of hover exit events. + */ + class SendHoverExitDelayed implements Runnable { + private final String LOG_TAG_SEND_HOVER_DELAYED = "SendHoverExitDelayed"; + + private MotionEvent mPrototype; + private int mPointerIdBits; + private int mPolicyFlags; + + public void post(MotionEvent prototype, int pointerIdBits, int policyFlags) { + cancel(); + mPrototype = MotionEvent.obtain(prototype); + mPointerIdBits = pointerIdBits; + mPolicyFlags = policyFlags; + mHandler.postDelayed(this, mDetermineUserIntentTimeout); + } + + public void cancel() { + if (isPending()) { + mHandler.removeCallbacks(this); + clear(); + } + } private boolean isPending() { - return (mPrototype != null); + return mHandler.hasCallbacks(this); } private void clear() { - if (!isPending()) { - return; - } mPrototype.recycle(); mPrototype = null; mPointerIdBits = -1; @@ -1538,29 +1463,25 @@ class TouchExplorer implements EventStreamTransformation { public void forceSendAndRemove() { if (isPending()) { run(); - remove(); + cancel(); } } public void run() { if (DEBUG) { - Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event: " - + MotionEvent.actionToString(mHoverAction)); - Slog.d(LOG_TAG_SEND_HOVER_DELAYED, mGestureStarted ? - "touchExplorationGestureStarted" : "touchExplorationGestureEnded"); + Slog.d(LOG_TAG_SEND_HOVER_DELAYED, "Injecting motion event:" + + " ACTION_HOVER_EXIT"); } - if (mGestureStarted) { - sendAccessibilityEvent(AccessibilityEvent.TYPE_TOUCH_EXPLORATION_GESTURE_START); - } else { - if (!mSendTouchExplorationEndDelayed.isPending()) { - mSendTouchExplorationEndDelayed.post(); - } - if (mSendTouchInteractionEndDelayed.isPending()) { - mSendTouchInteractionEndDelayed.remove(); - mSendTouchInteractionEndDelayed.post(); - } + sendMotionEvent(mPrototype, MotionEvent.ACTION_HOVER_EXIT, + mPointerIdBits, mPolicyFlags); + if (!mSendTouchExplorationEndDelayed.isPending()) { + mSendTouchExplorationEndDelayed.cancel(); + mSendTouchExplorationEndDelayed.post(); + } + if (mSendTouchInteractionEndDelayed.isPending()) { + mSendTouchInteractionEndDelayed.cancel(); + mSendTouchInteractionEndDelayed.post(); } - sendMotionEvent(mPrototype, mHoverAction, mPointerIdBits, mPolicyFlags); clear(); } } @@ -1574,7 +1495,7 @@ class TouchExplorer implements EventStreamTransformation { mDelay = delay; } - public void remove() { + public void cancel() { mHandler.removeCallbacks(this); } @@ -1589,7 +1510,7 @@ class TouchExplorer implements EventStreamTransformation { public void forceSendAndRemove() { if (isPending()) { run(); - remove(); + cancel(); } } @@ -1736,15 +1657,6 @@ class TouchExplorer implements EventStreamTransformation { class ReceivedPointerTracker { private static final String LOG_TAG_RECEIVED_POINTER_TRACKER = "ReceivedPointerTracker"; - // The coefficient by which to multiply - // ViewConfiguration.#getScaledTouchSlop() - // to compute #mThresholdActivePointer. - private static final int COEFFICIENT_ACTIVE_POINTER = 2; - - // Pointers that moved less than mThresholdActivePointer - // are considered active i.e. are ignored. - private final double mThresholdActivePointer; - // Keep track of where and when a pointer went down. private final float[] mReceivedPointerDownX = new float[MAX_POINTER_COUNT]; private final float[] mReceivedPointerDownY = new float[MAX_POINTER_COUNT]; @@ -1756,36 +1668,19 @@ class TouchExplorer implements EventStreamTransformation { // The edge flags of the last received down event. private int mLastReceivedDownEdgeFlags; - // Which down pointers are active. - private int mActivePointers; - - // Primary active pointer which is either the first that went down - // or if it goes up the next active that most recently went down. - private int mPrimaryActivePointerId; - - // Flag indicating that there is at least one active pointer moving. - private boolean mHasMovingActivePointer; + // Primary pointer which is either the first that went down + // or if it goes up the next one that most recently went down. + private int mPrimaryPointerId; // Keep track of the last up pointer data. private long mLastReceivedUpPointerDownTime; private int mLastReceivedUpPointerId; - private boolean mLastReceivedUpPointerActive; private float mLastReceivedUpPointerDownX; private float mLastReceivedUpPointerDownY; private MotionEvent mLastReceivedEvent; /** - * Creates a new instance. - * - * @param context Context for looking up resources. - */ - public ReceivedPointerTracker(Context context) { - mThresholdActivePointer = - ViewConfiguration.get(context).getScaledTouchSlop() * COEFFICIENT_ACTIVE_POINTER; - } - - /** * Clears the internals state. */ public void clear() { @@ -1793,12 +1688,9 @@ class TouchExplorer implements EventStreamTransformation { Arrays.fill(mReceivedPointerDownY, 0); Arrays.fill(mReceivedPointerDownTime, 0); mReceivedPointersDown = 0; - mActivePointers = 0; - mPrimaryActivePointerId = 0; - mHasMovingActivePointer = false; + mPrimaryPointerId = 0; mLastReceivedUpPointerDownTime = 0; mLastReceivedUpPointerId = 0; - mLastReceivedUpPointerActive = false; mLastReceivedUpPointerDownX = 0; mLastReceivedUpPointerDownY = 0; } @@ -1822,9 +1714,6 @@ class TouchExplorer implements EventStreamTransformation { case MotionEvent.ACTION_POINTER_DOWN: { handleReceivedPointerDown(event.getActionIndex(), event); } break; - case MotionEvent.ACTION_MOVE: { - handleReceivedPointerMove(event); - } break; case MotionEvent.ACTION_UP: { handleReceivedPointerUp(event.getActionIndex(), event); } break; @@ -1833,7 +1722,7 @@ class TouchExplorer implements EventStreamTransformation { } break; } if (DEBUG) { - Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer: " + toString()); + Slog.i(LOG_TAG_RECEIVED_POINTER_TRACKER, "Received pointer:\n" + toString()); } } @@ -1852,20 +1741,6 @@ class TouchExplorer implements EventStreamTransformation { } /** - * @return The bits of the pointers that are active. - */ - public int getActivePointers() { - return mActivePointers; - } - - /** - * @return The number of down input pointers that are active. - */ - public int getActivePointerCount() { - return Integer.bitCount(mActivePointers); - } - - /** * Whether an received pointer is down. * * @param pointerId The unique pointer id. @@ -1877,17 +1752,6 @@ class TouchExplorer implements EventStreamTransformation { } /** - * Whether an input pointer is active. - * - * @param pointerId The unique pointer id. - * @return True if the pointer is active. - */ - public boolean isActivePointer(int pointerId) { - final int pointerFlag = (1 << pointerId); - return (mActivePointers & pointerFlag) != 0; - } - - /** * @param pointerId The unique pointer id. * @return The X coordinate where the pointer went down. */ @@ -1914,11 +1778,11 @@ class TouchExplorer implements EventStreamTransformation { /** * @return The id of the primary pointer. */ - public int getPrimaryActivePointerId() { - if (mPrimaryActivePointerId == INVALID_POINTER_ID) { - mPrimaryActivePointerId = findPrimaryActivePointer(); + public int getPrimaryPointerId() { + if (mPrimaryPointerId == INVALID_POINTER_ID) { + mPrimaryPointerId = findPrimaryPointerId(); } - return mPrimaryActivePointerId; + return mPrimaryPointerId; } /** @@ -1929,14 +1793,6 @@ class TouchExplorer implements EventStreamTransformation { } /** - * @return The id of the last received pointer that went up. - */ - public int getLastReceivedUpPointerId() { - return mLastReceivedUpPointerId; - } - - - /** * @return The down X of the last received pointer that went up. */ public float getLastReceivedUpPointerDownX() { @@ -1958,39 +1814,6 @@ class TouchExplorer implements EventStreamTransformation { } /** - * @return Whether the last received pointer that went up was active. - */ - public boolean wasLastReceivedUpPointerActive() { - return mLastReceivedUpPointerActive; - } - /** - * Populates the active pointer IDs to the given array. - * <p> - * Note: The client is responsible for providing large enough array. - * - * @param outPointerIds The array to which to write the active pointers. - */ - public void populateActivePointerIds(int[] outPointerIds) { - int index = 0; - for (int idBits = mActivePointers; idBits != 0; ) { - final int id = Integer.numberOfTrailingZeros(idBits); - idBits &= ~(1 << id); - outPointerIds[index] = id; - index++; - } - } - - /** - * @param pointerId The unique pointer id. - * @return Whether the pointer is active or was the last active than went up. - */ - public boolean isActiveOrWasLastActiveUpPointer(int pointerId) { - return (isActivePointer(pointerId) - || (mLastReceivedUpPointerId == pointerId - && mLastReceivedUpPointerActive)); - } - - /** * Handles a received pointer down event. * * @param pointerIndex The index of the pointer that has changed. @@ -2002,7 +1825,6 @@ class TouchExplorer implements EventStreamTransformation { mLastReceivedUpPointerId = 0; mLastReceivedUpPointerDownTime = 0; - mLastReceivedUpPointerActive = false; mLastReceivedUpPointerDownX = 0; mLastReceivedUpPointerDownX = 0; @@ -2013,25 +1835,7 @@ class TouchExplorer implements EventStreamTransformation { mReceivedPointerDownY[pointerId] = event.getY(pointerIndex); mReceivedPointerDownTime[pointerId] = event.getEventTime(); - if (!mHasMovingActivePointer) { - // If still no moving active pointers every - // down pointer is the only active one. - mActivePointers = pointerFlag; - mPrimaryActivePointerId = pointerId; - } else { - // If at least one moving active pointer every - // subsequent down pointer is active. - mActivePointers |= pointerFlag; - } - } - - /** - * Handles a received pointer move event. - * - * @param event The event to be handled. - */ - private void handleReceivedPointerMove(MotionEvent event) { - detectActivePointers(event); + mPrimaryPointerId = pointerId; } /** @@ -2046,80 +1850,38 @@ class TouchExplorer implements EventStreamTransformation { mLastReceivedUpPointerId = pointerId; mLastReceivedUpPointerDownTime = getReceivedPointerDownTime(pointerId); - mLastReceivedUpPointerActive = isActivePointer(pointerId); mLastReceivedUpPointerDownX = mReceivedPointerDownX[pointerId]; mLastReceivedUpPointerDownY = mReceivedPointerDownY[pointerId]; mReceivedPointersDown &= ~pointerFlag; - mActivePointers &= ~pointerFlag; mReceivedPointerDownX[pointerId] = 0; mReceivedPointerDownY[pointerId] = 0; mReceivedPointerDownTime[pointerId] = 0; - if (mActivePointers == 0) { - mHasMovingActivePointer = false; - } - if (mPrimaryActivePointerId == pointerId) { - mPrimaryActivePointerId = INVALID_POINTER_ID; - } - } - - /** - * Detects the active pointers in an event. - * - * @param event The event to examine. - */ - private void detectActivePointers(MotionEvent event) { - for (int i = 0, count = event.getPointerCount(); i < count; i++) { - final int pointerId = event.getPointerId(i); - if (mHasMovingActivePointer) { - // If already active => nothing to do. - if (isActivePointer(pointerId)) { - continue; - } - } - // Active pointers are ones that moved more than a given threshold. - final float pointerDeltaMove = computePointerDeltaMove(i, event); - if (pointerDeltaMove > mThresholdActivePointer) { - final int pointerFlag = (1 << pointerId); - mActivePointers |= pointerFlag; - mHasMovingActivePointer = true; - } + if (mPrimaryPointerId == pointerId) { + mPrimaryPointerId = INVALID_POINTER_ID; } } /** - * @return The primary active pointer. + * @return The primary pointer id. */ - private int findPrimaryActivePointer() { - int primaryActivePointerId = INVALID_POINTER_ID; + private int findPrimaryPointerId() { + int primaryPointerId = INVALID_POINTER_ID; long minDownTime = Long.MAX_VALUE; - // Find the active pointer that went down first. - for (int i = 0, count = mReceivedPointerDownTime.length; i < count; i++) { - if (isActivePointer(i)) { - final long downPointerTime = mReceivedPointerDownTime[i]; - if (downPointerTime < minDownTime) { - minDownTime = downPointerTime; - primaryActivePointerId = i; - } + + // Find the pointer that went down first. + int pointerIdBits = mReceivedPointersDown; + while (pointerIdBits > 0) { + final int pointerId = Integer.numberOfTrailingZeros(pointerIdBits); + pointerIdBits &= ~(1 << pointerId); + final long downPointerTime = mReceivedPointerDownTime[pointerId]; + if (downPointerTime < minDownTime) { + minDownTime = downPointerTime; + primaryPointerId = pointerId; } } - return primaryActivePointerId; - } - - /** - * Computes the move for a given action pointer index since the - * corresponding pointer went down. - * - * @param pointerIndex The action pointer index. - * @param event The event to examine. - * @return The distance the pointer has moved. - */ - private float computePointerDeltaMove(int pointerIndex, MotionEvent event) { - final int pointerId = event.getPointerId(pointerIndex); - final float deltaX = event.getX(pointerIndex) - mReceivedPointerDownX[pointerId]; - final float deltaY = event.getY(pointerIndex) - mReceivedPointerDownY[pointerId]; - return (float) Math.hypot(deltaX, deltaY); + return primaryPointerId; } @Override @@ -2136,18 +1898,8 @@ class TouchExplorer implements EventStreamTransformation { } } builder.append("]"); - builder.append("\nActive pointers #"); - builder.append(getActivePointerCount()); - builder.append(" [ "); - for (int i = 0; i < MAX_POINTER_COUNT; i++) { - if (isActivePointer(i)) { - builder.append(i); - builder.append(" "); - } - } - builder.append("]"); - builder.append("\nPrimary active pointer id [ "); - builder.append(getPrimaryActivePointerId()); + builder.append("\nPrimary pointer id [ "); + builder.append(getPrimaryPointerId()); builder.append(" ]"); builder.append("\n========================="); return builder.toString(); diff --git a/services/java/com/android/server/accounts/AccountManagerService.java b/services/java/com/android/server/accounts/AccountManagerService.java index 0fbde37a20c5..f972f70f41e2 100644 --- a/services/java/com/android/server/accounts/AccountManagerService.java +++ b/services/java/com/android/server/accounts/AccountManagerService.java @@ -47,6 +47,7 @@ import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.RegisteredServicesCache; import android.content.pm.RegisteredServicesCacheListener; +import android.content.pm.ResolveInfo; import android.content.pm.UserInfo; import android.database.Cursor; import android.database.DatabaseUtils; @@ -56,10 +57,10 @@ import android.os.Binder; import android.os.Bundle; import android.os.Environment; import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; +import android.os.Parcel; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -74,6 +75,7 @@ import android.util.SparseArray; import com.android.internal.R; import com.android.internal.util.ArrayUtils; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.FgThread; import com.google.android.collect.Lists; import com.google.android.collect.Sets; @@ -113,7 +115,6 @@ public class AccountManagerService private final PackageManager mPackageManager; private UserManager mUserManager; - private HandlerThread mMessageThread; private final MessageHandler mMessageHandler; // Messages that can be sent on mHandler @@ -234,9 +235,7 @@ public class AccountManagerService mContext = context; mPackageManager = packageManager; - mMessageThread = new HandlerThread("AccountManagerService"); - mMessageThread.start(); - mMessageHandler = new MessageHandler(mMessageThread.getLooper()); + mMessageHandler = new MessageHandler(FgThread.get().getLooper()); mAuthenticatorCache = authenticatorCache; mAuthenticatorCache.setListener(this, null /* Handler */); @@ -269,6 +268,21 @@ public class AccountManagerService }, UserHandle.ALL, userFilter, null, null); } + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + try { + return super.onTransact(code, data, reply, flags); + } catch (RuntimeException e) { + // The account manager only throws security exceptions, so let's + // log all others. + if (!(e instanceof SecurityException)) { + Slog.wtf(TAG, "Account Manager Crash", e); + } + throw e; + } + } + public void systemReady() { } @@ -279,17 +293,16 @@ public class AccountManagerService return mUserManager; } - private UserAccounts initUser(int userId) { - synchronized (mUsers) { - UserAccounts accounts = mUsers.get(userId); - if (accounts == null) { - accounts = new UserAccounts(mContext, userId); - mUsers.append(userId, accounts); - purgeOldGrants(accounts); - validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */); - } - return accounts; + /* Caller should lock mUsers */ + private UserAccounts initUserLocked(int userId) { + UserAccounts accounts = mUsers.get(userId); + if (accounts == null) { + accounts = new UserAccounts(mContext, userId); + mUsers.append(userId, accounts); + purgeOldGrants(accounts); + validateAccountsInternal(accounts, true /* invalidateAuthenticatorCache */); } + return accounts; } private void purgeOldGrantsAll() { @@ -413,7 +426,7 @@ public class AccountManagerService synchronized (mUsers) { UserAccounts accounts = mUsers.get(userId); if (accounts == null) { - accounts = initUser(userId); + accounts = initUserLocked(userId); mUsers.append(userId, accounts); } return accounts; @@ -583,15 +596,18 @@ public class AccountManagerService try { new Session(fromAccounts, null, account.type, false, false /* stripAuthTokenFromResult */) { + @Override protected String toDebugString(long now) { return super.toDebugString(now) + ", getAccountCredentialsForClone" + ", " + account.type; } + @Override public void run() throws RemoteException { mAuthenticator.getAccountCredentialsForCloning(this, account); } + @Override public void onResult(Bundle result) { if (result != null) { if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) { @@ -616,11 +632,13 @@ public class AccountManagerService try { new Session(targetUser, null, account.type, false, false /* stripAuthTokenFromResult */) { + @Override protected String toDebugString(long now) { return super.toDebugString(now) + ", getAccountCredentialsForClone" + ", " + account.type; } + @Override public void run() throws RemoteException { // Confirm that the owner's account still exists before this step. UserAccounts owner = getUserAccounts(UserHandle.USER_OWNER); @@ -635,6 +653,7 @@ public class AccountManagerService } } + @Override public void onResult(Bundle result) { if (result != null) { if (result.getBoolean(AccountManager.KEY_BOOLEAN_RESULT, false)) { @@ -649,6 +668,7 @@ public class AccountManagerService } } + @Override public void onError(int errorCode, String errorMessage) { super.onError(errorCode, errorMessage); // TODO: Show error notification to user @@ -777,6 +797,7 @@ public class AccountManagerService mAccount = account; } + @Override public void run() throws RemoteException { try { mAuthenticator.hasFeatures(this, mAccount, mFeatures); @@ -785,6 +806,7 @@ public class AccountManagerService } } + @Override public void onResult(Bundle result) { IAccountManagerResponse response = getResponseAndClose(); if (response != null) { @@ -810,6 +832,7 @@ public class AccountManagerService } } + @Override protected String toDebugString(long now) { return super.toDebugString(now) + ", hasFeatures" + ", " + mAccount @@ -866,15 +889,18 @@ public class AccountManagerService mAccount = account; } + @Override protected String toDebugString(long now) { return super.toDebugString(now) + ", removeAccount" + ", account " + mAccount; } + @Override public void run() throws RemoteException { mAuthenticator.getAccountRemovalAllowed(this, mAccount); } + @Override public void onResult(Bundle result) { if (result != null && result.containsKey(AccountManager.KEY_BOOLEAN_RESULT) && !result.containsKey(AccountManager.KEY_INTENT)) { @@ -1215,16 +1241,19 @@ public class AccountManagerService try { new Session(accounts, response, accountType, false, false /* stripAuthTokenFromResult */) { + @Override protected String toDebugString(long now) { return super.toDebugString(now) + ", getAuthTokenLabel" + ", " + accountType + ", authTokenType " + authTokenType; } + @Override public void run() throws RemoteException { mAuthenticator.getAuthTokenLabel(this, authTokenType); } + @Override public void onResult(Bundle result) { if (result != null) { String label = result.getString(AccountManager.KEY_AUTH_TOKEN_LABEL); @@ -1302,6 +1331,7 @@ public class AccountManagerService new Session(accounts, response, account.type, expectActivityLaunch, false /* stripAuthTokenFromResult */) { + @Override protected String toDebugString(long now) { if (loginOptions != null) loginOptions.keySet(); return super.toDebugString(now) + ", getAuthToken" @@ -1311,6 +1341,7 @@ public class AccountManagerService + ", notifyOnAuthFailure " + notifyOnAuthFailure; } + @Override public void run() throws RemoteException { // If the caller doesn't have permission then create and return the // "grant permission" intent instead of the "getAuthToken" intent. @@ -1321,6 +1352,7 @@ public class AccountManagerService } } + @Override public void onResult(Bundle result) { if (result != null) { if (result.containsKey(AccountManager.KEY_AUTH_TOKEN_LABEL)) { @@ -1485,11 +1517,13 @@ public class AccountManagerService try { new Session(accounts, response, accountType, expectActivityLaunch, true /* stripAuthTokenFromResult */) { + @Override public void run() throws RemoteException { mAuthenticator.addAccount(this, mAccountType, authTokenType, requiredFeatures, options); } + @Override protected String toDebugString(long now) { return super.toDebugString(now) + ", addAccount" + ", accountType " + accountType @@ -1510,7 +1544,10 @@ public class AccountManagerService int userId) { // Only allow the system process to read accounts of other users if (userId != UserHandle.getCallingUserId() - && Binder.getCallingUid() != Process.myUid()) { + && Binder.getCallingUid() != Process.myUid() + && mContext.checkCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("User " + UserHandle.getCallingUserId() + " trying to confirm account credentials for " + userId); } @@ -1530,9 +1567,11 @@ public class AccountManagerService try { new Session(accounts, response, account.type, expectActivityLaunch, true /* stripAuthTokenFromResult */) { + @Override public void run() throws RemoteException { mAuthenticator.confirmCredentials(this, account, options); } + @Override protected String toDebugString(long now) { return super.toDebugString(now) + ", confirmCredentials" + ", " + account; @@ -1563,9 +1602,11 @@ public class AccountManagerService try { new Session(accounts, response, account.type, expectActivityLaunch, true /* stripAuthTokenFromResult */) { + @Override public void run() throws RemoteException { mAuthenticator.updateCredentials(this, account, authTokenType, loginOptions); } + @Override protected String toDebugString(long now) { if (loginOptions != null) loginOptions.keySet(); return super.toDebugString(now) + ", updateCredentials" @@ -1596,9 +1637,11 @@ public class AccountManagerService try { new Session(accounts, response, accountType, expectActivityLaunch, true /* stripAuthTokenFromResult */) { + @Override public void run() throws RemoteException { mAuthenticator.editProperties(this, mAccountType); } + @Override protected String toDebugString(long now) { return super.toDebugString(now) + ", editProperties" + ", accountType " + accountType; @@ -1624,6 +1667,7 @@ public class AccountManagerService mFeatures = features; } + @Override public void run() throws RemoteException { synchronized (mAccounts.cacheLock) { mAccountsOfType = getAccountsFromCacheLocked(mAccounts, mAccountType, mCallingUid, @@ -1661,6 +1705,7 @@ public class AccountManagerService } } + @Override public void onResult(Bundle result) { mNumResults++; if (result == null) { @@ -1699,6 +1744,7 @@ public class AccountManagerService } + @Override protected String toDebugString(long now) { return super.toDebugString(now) + ", getAccountsByTypeAndFeatures" + ", " + (mFeatures != null ? TextUtils.join(",", mFeatures) : null); @@ -1751,16 +1797,14 @@ public class AccountManagerService private AccountAndUser[] getAccounts(int[] userIds) { final ArrayList<AccountAndUser> runningAccounts = Lists.newArrayList(); - synchronized (mUsers) { - for (int userId : userIds) { - UserAccounts userAccounts = getUserAccounts(userId); - if (userAccounts == null) continue; - synchronized (userAccounts.cacheLock) { - Account[] accounts = getAccountsFromCacheLocked(userAccounts, null, - Binder.getCallingUid(), null); - for (int a = 0; a < accounts.length; a++) { - runningAccounts.add(new AccountAndUser(accounts[a], userId)); - } + for (int userId : userIds) { + UserAccounts userAccounts = getUserAccounts(userId); + if (userAccounts == null) continue; + synchronized (userAccounts.cacheLock) { + Account[] accounts = getAccountsFromCacheLocked(userAccounts, null, + Binder.getCallingUid(), null); + for (int a = 0; a < accounts.length; a++) { + runningAccounts.add(new AccountAndUser(accounts[a], userId)); } } } @@ -1779,7 +1823,10 @@ public class AccountManagerService int callingUid = Binder.getCallingUid(); // Only allow the system process to read accounts of other users if (userId != UserHandle.getCallingUserId() - && callingUid != Process.myUid()) { + && callingUid != Process.myUid() + && mContext.checkCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("User " + UserHandle.getCallingUserId() + " trying to get account for " + userId); } @@ -2108,9 +2155,36 @@ public class AccountManagerService } } + @Override public void onResult(Bundle result) { mNumResults++; - if (result != null && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) { + Intent intent = null; + if (result != null + && (intent = result.getParcelable(AccountManager.KEY_INTENT)) != null) { + /* + * The Authenticator API allows third party authenticators to + * supply arbitrary intents to other apps that they can run, + * this can be very bad when those apps are in the system like + * the System Settings. + */ + int authenticatorUid = Binder.getCallingUid(); + long bid = Binder.clearCallingIdentity(); + try { + PackageManager pm = mContext.getPackageManager(); + ResolveInfo resolveInfo = pm.resolveActivityAsUser(intent, 0, mAccounts.userId); + int targetUid = resolveInfo.activityInfo.applicationInfo.uid; + if (PackageManager.SIGNATURE_MATCH != + pm.checkSignatures(authenticatorUid, targetUid)) { + throw new SecurityException( + "Activity to be started with KEY_INTENT must " + + "share Authenticator's signatures"); + } + } finally { + Binder.restoreCallingIdentity(bid); + } + } + if (result != null + && !TextUtils.isEmpty(result.getString(AccountManager.KEY_AUTHTOKEN))) { String accountName = result.getString(AccountManager.KEY_ACCOUNT_NAME); String accountType = result.getString(AccountManager.KEY_ACCOUNT_TYPE); if (!TextUtils.isEmpty(accountName) && !TextUtils.isEmpty(accountType)) { @@ -2220,6 +2294,7 @@ public class AccountManagerService super(looper); } + @Override public void handleMessage(Message msg) { switch (msg.what) { case MESSAGE_TIMED_OUT: @@ -2537,7 +2612,7 @@ public class AccountManagerService return userId; } - private boolean inSystemImage(int callingUid) { + private boolean isPrivileged(int callingUid) { final int callingUserId = UserHandle.getUserId(callingUid); final PackageManager userPackageManager; @@ -2553,7 +2628,7 @@ public class AccountManagerService try { PackageInfo packageInfo = userPackageManager.getPackageInfo(name, 0 /* flags */); if (packageInfo != null - && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + && (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0) { return true; } } catch (PackageManager.NameNotFoundException e) { @@ -2564,7 +2639,7 @@ public class AccountManagerService } private boolean permissionIsGranted(Account account, String authTokenType, int callerUid) { - final boolean inSystemImage = inSystemImage(callerUid); + final boolean isPrivileged = isPrivileged(callerUid); final boolean fromAuthenticator = account != null && hasAuthenticatorUid(account.type, callerUid); final boolean hasExplicitGrants = account != null @@ -2575,7 +2650,7 @@ public class AccountManagerService + ": is authenticator? " + fromAuthenticator + ", has explicit permission? " + hasExplicitGrants); } - return fromAuthenticator || hasExplicitGrants || inSystemImage; + return fromAuthenticator || hasExplicitGrants || isPrivileged; } private boolean hasAuthenticatorUid(String accountType, int callingUid) { @@ -2785,7 +2860,8 @@ public class AccountManagerService || callingUid == Process.myUid()) { return unfiltered; } - if (mUserManager.getUserInfo(userAccounts.userId).isRestricted()) { + UserInfo user = mUserManager.getUserInfo(userAccounts.userId); + if (user != null && user.isRestricted()) { String[] packages = mPackageManager.getPackagesForUid(callingUid); // If any of the packages is a white listed package, return the full set, // otherwise return non-shared accounts only. diff --git a/services/java/com/android/server/am/ActiveServices.java b/services/java/com/android/server/am/ActiveServices.java index 5c24e6781402..a64940ca2420 100644 --- a/services/java/com/android/server/am/ActiveServices.java +++ b/services/java/com/android/server/am/ActiveServices.java @@ -20,13 +20,16 @@ import java.io.FileDescriptor; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; +import android.os.Handler; +import android.os.Looper; +import android.util.ArrayMap; +import com.android.internal.app.ProcessStats; import com.android.internal.os.BatteryStatsImpl; +import com.android.internal.os.TransferPipe; import com.android.server.am.ActivityManagerService.ItemMatcher; import com.android.server.am.ActivityManagerService.NeededUriGrants; @@ -52,14 +55,15 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.util.EventLog; -import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; -public class ActiveServices { +public final class ActiveServices { static final boolean DEBUG_SERVICE = ActivityManagerService.DEBUG_SERVICE; static final boolean DEBUG_SERVICE_EXECUTING = ActivityManagerService.DEBUG_SERVICE_EXECUTING; + static final boolean DEBUG_DELAYED_SERVICE = ActivityManagerService.DEBUG_SERVICE; + static final boolean DEBUG_DELAYED_STATS = DEBUG_DELAYED_SERVICE; static final boolean DEBUG_MU = ActivityManagerService.DEBUG_MU; static final String TAG = ActivityManagerService.TAG; static final String TAG_MU = ActivityManagerService.TAG_MU; @@ -67,6 +71,9 @@ public class ActiveServices { // How long we wait for a service to finish executing. static final int SERVICE_TIMEOUT = 20*1000; + // How long we wait for a service to finish executing. + static final int SERVICE_BACKGROUND_TIMEOUT = SERVICE_TIMEOUT * 10; + // How long a service needs to be running until restarting its process // is no longer considered to be a relaunch of the service. static final int SERVICE_RESTART_DURATION = 5*1000; @@ -89,16 +96,24 @@ public class ActiveServices { // LRU background list. static final int MAX_SERVICE_INACTIVITY = 30*60*1000; + // How long we wait for a background started service to stop itself before + // allowing the next pending start to run. + static final int BG_START_TIMEOUT = 15*1000; + final ActivityManagerService mAm; - final ServiceMap mServiceMap = new ServiceMap(); + // Maximum number of services that we allow to start in the background + // at the same time. + final int mMaxStartingBackground; + + final SparseArray<ServiceMap> mServiceMap = new SparseArray<ServiceMap>(); /** * All currently bound service connections. Keys are the IBinder of * the client's IServiceConnection. */ - final HashMap<IBinder, ArrayList<ConnectionRecord>> mServiceConnections - = new HashMap<IBinder, ArrayList<ConnectionRecord>>(); + final ArrayMap<IBinder, ArrayList<ConnectionRecord>> mServiceConnections + = new ArrayMap<IBinder, ArrayList<ConnectionRecord>>(); /** * List of services that we have been asked to start, @@ -116,104 +131,149 @@ public class ActiveServices { = new ArrayList<ServiceRecord>(); /** - * List of services that are in the process of being stopped. + * List of services that are in the process of being destroyed. */ - final ArrayList<ServiceRecord> mStoppingServices + final ArrayList<ServiceRecord> mDestroyingServices = new ArrayList<ServiceRecord>(); - static class ServiceMap { - - private final SparseArray<HashMap<ComponentName, ServiceRecord>> mServicesByNamePerUser - = new SparseArray<HashMap<ComponentName, ServiceRecord>>(); - private final SparseArray<HashMap<Intent.FilterComparison, ServiceRecord>> - mServicesByIntentPerUser = new SparseArray< - HashMap<Intent.FilterComparison, ServiceRecord>>(); - - ServiceRecord getServiceByName(ComponentName name, int callingUser) { - // TODO: Deal with global services - if (DEBUG_MU) - Slog.v(TAG_MU, "getServiceByName(" + name + "), callingUser = " + callingUser); - return getServices(callingUser).get(name); - } - - ServiceRecord getServiceByName(ComponentName name) { - return getServiceByName(name, -1); - } - - ServiceRecord getServiceByIntent(Intent.FilterComparison filter, int callingUser) { - // TODO: Deal with global services - if (DEBUG_MU) - Slog.v(TAG_MU, "getServiceByIntent(" + filter + "), callingUser = " + callingUser); - return getServicesByIntent(callingUser).get(filter); - } - - ServiceRecord getServiceByIntent(Intent.FilterComparison filter) { - return getServiceByIntent(filter, -1); - } + static final class DelayingProcess extends ArrayList<ServiceRecord> { + long timeoout; + } - void putServiceByName(ComponentName name, int callingUser, ServiceRecord value) { - // TODO: Deal with global services - getServices(callingUser).put(name, value); + /** + * Information about services for a single user. + */ + class ServiceMap extends Handler { + final int mUserId; + final ArrayMap<ComponentName, ServiceRecord> mServicesByName + = new ArrayMap<ComponentName, ServiceRecord>(); + final ArrayMap<Intent.FilterComparison, ServiceRecord> mServicesByIntent + = new ArrayMap<Intent.FilterComparison, ServiceRecord>(); + + final ArrayList<ServiceRecord> mDelayedStartList + = new ArrayList<ServiceRecord>(); + /* XXX eventually I'd like to have this based on processes instead of services. + * That is, if we try to start two services in a row both running in the same + * process, this should be one entry in mStartingBackground for that one process + * that remains until all services in it are done. + final ArrayMap<ProcessRecord, DelayingProcess> mStartingBackgroundMap + = new ArrayMap<ProcessRecord, DelayingProcess>(); + final ArrayList<DelayingProcess> mStartingProcessList + = new ArrayList<DelayingProcess>(); + */ + + final ArrayList<ServiceRecord> mStartingBackground + = new ArrayList<ServiceRecord>(); + + static final int MSG_BG_START_TIMEOUT = 1; + + ServiceMap(Looper looper, int userId) { + super(looper); + mUserId = userId; + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_BG_START_TIMEOUT: { + synchronized (mAm) { + rescheduleDelayedStarts(); + } + } break; + } } - void putServiceByIntent(Intent.FilterComparison filter, int callingUser, - ServiceRecord value) { - // TODO: Deal with global services - getServicesByIntent(callingUser).put(filter, value); + void ensureNotStartingBackground(ServiceRecord r) { + if (mStartingBackground.remove(r)) { + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "No longer background starting: " + r); + rescheduleDelayedStarts(); + } + if (mDelayedStartList.remove(r)) { + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "No longer delaying start: " + r); + } } - void removeServiceByName(ComponentName name, int callingUser) { - // TODO: Deal with global services - ServiceRecord removed = getServices(callingUser).remove(name); - if (DEBUG_MU) - Slog.v(TAG, "removeServiceByName user=" + callingUser + " name=" + name - + " removed=" + removed); + void rescheduleDelayedStarts() { + removeMessages(MSG_BG_START_TIMEOUT); + final long now = SystemClock.uptimeMillis(); + for (int i=0, N=mStartingBackground.size(); i<N; i++) { + ServiceRecord r = mStartingBackground.get(i); + if (r.startingBgTimeout <= now) { + Slog.i(TAG, "Waited long enough for: " + r); + mStartingBackground.remove(i); + N--; + } + } + while (mDelayedStartList.size() > 0 + && mStartingBackground.size() < mMaxStartingBackground) { + ServiceRecord r = mDelayedStartList.remove(0); + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (exec next): " + r); + if (r.pendingStarts.size() <= 0) { + Slog.w(TAG, "**** NO PENDING STARTS! " + r + " startReq=" + r.startRequested + + " delayedStop=" + r.delayedStop); + } + if (DEBUG_DELAYED_SERVICE) { + if (mDelayedStartList.size() > 0) { + Slog.v(TAG, "Remaining delayed list:"); + for (int i=0; i<mDelayedStartList.size(); i++) { + Slog.v(TAG, " #" + i + ": " + mDelayedStartList.get(i)); + } + } + } + r.delayed = false; + startServiceInnerLocked(this, r.pendingStarts.get(0).intent, r, false, true); + } + if (mStartingBackground.size() > 0) { + ServiceRecord next = mStartingBackground.get(0); + long when = next.startingBgTimeout > now ? next.startingBgTimeout : now; + if (DEBUG_DELAYED_SERVICE) Slog.v(TAG, "Top bg start is " + next + + ", can delay others up to " + when); + Message msg = obtainMessage(MSG_BG_START_TIMEOUT); + sendMessageAtTime(msg, when); + } + if (mStartingBackground.size() < mMaxStartingBackground) { + mAm.backgroundServicesFinishedLocked(mUserId); + } } + } - void removeServiceByIntent(Intent.FilterComparison filter, int callingUser) { - // TODO: Deal with global services - ServiceRecord removed = getServicesByIntent(callingUser).remove(filter); - if (DEBUG_MU) - Slog.v(TAG_MU, "removeServiceByIntent user=" + callingUser + " intent=" + filter - + " removed=" + removed); - } + public ActiveServices(ActivityManagerService service) { + mAm = service; + mMaxStartingBackground = ActivityManager.isLowRamDeviceStatic() ? 1 : 3; + } - Collection<ServiceRecord> getAllServices(int callingUser) { - // TODO: Deal with global services - return getServices(callingUser).values(); - } + ServiceRecord getServiceByName(ComponentName name, int callingUser) { + // TODO: Deal with global services + if (DEBUG_MU) + Slog.v(TAG_MU, "getServiceByName(" + name + "), callingUser = " + callingUser); + return getServiceMap(callingUser).mServicesByName.get(name); + } - private HashMap<ComponentName, ServiceRecord> getServices(int callingUser) { - HashMap<ComponentName, ServiceRecord> map = mServicesByNamePerUser.get(callingUser); - if (map == null) { - map = new HashMap<ComponentName, ServiceRecord>(); - mServicesByNamePerUser.put(callingUser, map); - } - return map; - } + boolean hasBackgroundServices(int callingUser) { + ServiceMap smap = mServiceMap.get(callingUser); + return smap != null ? smap.mStartingBackground.size() >= mMaxStartingBackground : false; + } - private HashMap<Intent.FilterComparison, ServiceRecord> getServicesByIntent( - int callingUser) { - HashMap<Intent.FilterComparison, ServiceRecord> map - = mServicesByIntentPerUser.get(callingUser); - if (map == null) { - map = new HashMap<Intent.FilterComparison, ServiceRecord>(); - mServicesByIntentPerUser.put(callingUser, map); - } - return map; + private ServiceMap getServiceMap(int callingUser) { + ServiceMap smap = mServiceMap.get(callingUser); + if (smap == null) { + smap = new ServiceMap(mAm.mHandler.getLooper(), callingUser); + mServiceMap.put(callingUser, smap); } + return smap; } - public ActiveServices(ActivityManagerService service) { - mAm = service; + ArrayMap<ComponentName, ServiceRecord> getServices(int callingUser) { + return getServiceMap(callingUser).mServicesByName; } ComponentName startServiceLocked(IApplicationThread caller, Intent service, String resolvedType, int callingPid, int callingUid, int userId) { - if (DEBUG_SERVICE) Slog.v(TAG, "startService: " + service + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "startService: " + service + " type=" + resolvedType + " args=" + service.getExtras()); + final boolean callerFg; if (caller != null) { final ProcessRecord callerApp = mAm.getRecordForAppLocked(caller); if (callerApp == null) { @@ -222,11 +282,15 @@ public class ActiveServices { + " (pid=" + Binder.getCallingPid() + ") when starting service " + service); } + callerFg = callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE; + } else { + callerFg = true; } + ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, - callingPid, callingUid, userId, true); + callingPid, callingUid, userId, true, callerFg); if (res == null) { return null; } @@ -240,28 +304,132 @@ public class ActiveServices { if (unscheduleServiceRestartLocked(r)) { if (DEBUG_SERVICE) Slog.v(TAG, "START SERVICE WHILE RESTART PENDING: " + r); } + r.lastActivity = SystemClock.uptimeMillis(); r.startRequested = true; - r.callStart = false; + r.delayedStop = false; r.pendingStarts.add(new ServiceRecord.StartItem(r, false, r.makeNextStartId(), service, neededGrants)); - r.lastActivity = SystemClock.uptimeMillis(); + + final ServiceMap smap = getServiceMap(r.userId); + boolean addToStarting = false; + if (!callerFg && r.app == null && mAm.mStartedUsers.get(r.userId) != null) { + ProcessRecord proc = mAm.getProcessRecordLocked(r.processName, r.appInfo.uid, false); + if (proc == null || proc.curProcState > ActivityManager.PROCESS_STATE_RECEIVER) { + // If this is not coming from a foreground caller, then we may want + // to delay the start if there are already other background services + // that are starting. This is to avoid process start spam when lots + // of applications are all handling things like connectivity broadcasts. + // We only do this for cached processes, because otherwise an application + // can have assumptions about calling startService() for a service to run + // in its own process, and for that process to not be killed before the + // service is started. This is especially the case for receivers, which + // may start a service in onReceive() to do some additional work and have + // initialized some global state as part of that. + if (DEBUG_DELAYED_SERVICE) Slog.v(TAG, "Potential start delay of " + r + " in " + + proc); + if (r.delayed) { + // This service is already scheduled for a delayed start; just leave + // it still waiting. + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Continuing to delay: " + r); + return r.name; + } + if (smap.mStartingBackground.size() >= mMaxStartingBackground) { + // Something else is starting, delay! + Slog.i(TAG, "Delaying start of: " + r); + smap.mDelayedStartList.add(r); + r.delayed = true; + return r.name; + } + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Not delaying: " + r); + addToStarting = true; + } else if (proc.curProcState >= ActivityManager.PROCESS_STATE_SERVICE) { + // We slightly loosen when we will enqueue this new service as a background + // starting service we are waiting for, to also include processes that are + // currently running other services or receivers. + addToStarting = true; + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Not delaying, but counting as bg: " + r); + } else if (DEBUG_DELAYED_STATS) { + StringBuilder sb = new StringBuilder(128); + sb.append("Not potential delay (state=").append(proc.curProcState) + .append(' ').append(proc.adjType); + String reason = proc.makeAdjReason(); + if (reason != null) { + sb.append(' '); + sb.append(reason); + } + sb.append("): "); + sb.append(r.toString()); + Slog.v(TAG, sb.toString()); + } + } else if (DEBUG_DELAYED_STATS) { + if (callerFg) { + Slog.v(TAG, "Not potential delay (callerFg=" + callerFg + " uid=" + + callingUid + " pid=" + callingPid + "): " + r); + } else if (r.app != null) { + Slog.v(TAG, "Not potential delay (cur app=" + r.app + "): " + r); + } else { + Slog.v(TAG, "Not potential delay (user " + r.userId + " not started): " + r); + } + } + + return startServiceInnerLocked(smap, service, r, callerFg, addToStarting); + } + + ComponentName startServiceInnerLocked(ServiceMap smap, Intent service, + ServiceRecord r, boolean callerFg, boolean addToStarting) { + ProcessStats.ServiceState stracker = r.getTracker(); + if (stracker != null) { + stracker.setStarted(true, mAm.mProcessStats.getMemFactorLocked(), r.lastActivity); + } + r.callStart = false; synchronized (r.stats.getBatteryStats()) { r.stats.startRunningLocked(); } - String error = bringUpServiceLocked(r, service.getFlags(), false); + String error = bringUpServiceLocked(r, service.getFlags(), callerFg, false); if (error != null) { return new ComponentName("!!", error); } + + if (r.startRequested && addToStarting) { + boolean first = smap.mStartingBackground.size() == 0; + smap.mStartingBackground.add(r); + r.startingBgTimeout = SystemClock.uptimeMillis() + BG_START_TIMEOUT; + if (DEBUG_DELAYED_SERVICE) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.v(TAG, "Starting background (first=" + first + "): " + r, here); + } else if (DEBUG_DELAYED_STATS) { + Slog.v(TAG, "Starting background (first=" + first + "): " + r); + } + if (first) { + smap.rescheduleDelayedStarts(); + } + } else if (callerFg) { + smap.ensureNotStartingBackground(r); + } + return r.name; } private void stopServiceLocked(ServiceRecord service) { + if (service.delayed) { + // If service isn't actually running, but is is being held in the + // delayed list, then we need to keep it started but note that it + // should be stopped once no longer delayed. + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Delaying stop of pending: " + service); + service.delayedStop = true; + return; + } synchronized (service.stats.getBatteryStats()) { service.stats.stopRunningLocked(); } service.startRequested = false; + if (service.tracker != null) { + service.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(), + SystemClock.uptimeMillis()); + } service.callStart = false; - bringDownServiceLocked(service, false); + bringDownServiceIfNeededLocked(service, false, false); } int stopServiceLocked(IApplicationThread caller, Intent service, @@ -279,7 +447,7 @@ public class ActiveServices { // If this service is active, make sure it is stopped. ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, - Binder.getCallingPid(), Binder.getCallingUid(), userId, false); + Binder.getCallingPid(), Binder.getCallingUid(), userId, false, false); if (r != null) { if (r.record != null) { final long origId = Binder.clearCallingIdentity(); @@ -299,7 +467,7 @@ public class ActiveServices { IBinder peekServiceLocked(Intent service, String resolvedType) { ServiceLookupResult r = retrieveServiceLocked(service, resolvedType, Binder.getCallingPid(), Binder.getCallingUid(), - UserHandle.getCallingUserId(), false); + UserHandle.getCallingUserId(), false, false); IBinder ret = null; if (r != null) { @@ -354,11 +522,15 @@ public class ActiveServices { synchronized (r.stats.getBatteryStats()) { r.stats.stopRunningLocked(); - r.startRequested = false; - r.callStart = false; } + r.startRequested = false; + if (r.tracker != null) { + r.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(), + SystemClock.uptimeMillis()); + } + r.callStart = false; final long origId = Binder.clearCallingIdentity(); - bringDownServiceLocked(r, false); + bringDownServiceIfNeededLocked(r, false, false); Binder.restoreCallingIdentity(origId); return true; } @@ -387,11 +559,12 @@ public class ActiveServices { if (r.app != null) { updateServiceForegroundLocked(r.app, true); } + getServiceMap(r.userId).ensureNotStartingBackground(r); } else { if (r.isForeground) { r.isForeground = false; if (r.app != null) { - mAm.updateLruProcessLocked(r.app, false); + mAm.updateLruProcessLocked(r.app, false, false); updateServiceForegroundLocked(r.app, true); } } @@ -409,7 +582,8 @@ public class ActiveServices { private void updateServiceForegroundLocked(ProcessRecord proc, boolean oomAdj) { boolean anyForeground = false; - for (ServiceRecord sr : proc.services) { + for (int i=proc.services.size()-1; i>=0; i--) { + ServiceRecord sr = proc.services.valueAt(i); if (sr.isForeground) { anyForeground = true; break; @@ -439,7 +613,7 @@ public class ActiveServices { ActivityRecord activity = null; if (token != null) { - activity = mAm.mMainStack.isInStackLocked(token); + activity = ActivityRecord.isInStackLocked(token); if (activity == null) { Slog.w(TAG, "Binding with unknown activity: " + token); return 0; @@ -469,9 +643,11 @@ public class ActiveServices { } } + final boolean callerFg = callerApp.setSchedGroup != Process.THREAD_GROUP_BG_NONINTERACTIVE; + ServiceLookupResult res = retrieveServiceLocked(service, resolvedType, - Binder.getCallingPid(), Binder.getCallingUid(), userId, true); + Binder.getCallingPid(), Binder.getCallingUid(), userId, true, callerFg); if (res == null) { return 0; } @@ -488,6 +664,18 @@ public class ActiveServices { + s); } + if ((flags&Context.BIND_AUTO_CREATE) != 0) { + s.lastActivity = SystemClock.uptimeMillis(); + if (!s.hasAutoCreateConnections()) { + // This is the first binding, let the tracker know. + ProcessStats.ServiceState stracker = s.getTracker(); + if (stracker != null) { + stracker.setBound(true, mAm.mProcessStats.getMemFactorLocked(), + s.lastActivity); + } + } + } + AppBindRecord b = s.retrieveAppBindingLocked(service, callerApp); ConnectionRecord c = new ConnectionRecord(b, activity, connection, flags, clientLabel, clientIntent); @@ -519,7 +707,7 @@ public class ActiveServices { if ((flags&Context.BIND_AUTO_CREATE) != 0) { s.lastActivity = SystemClock.uptimeMillis(); - if (bringUpServiceLocked(s, service.getFlags(), false) != null) { + if (bringUpServiceLocked(s, service.getFlags(), callerFg, false) != null) { return 0; } } @@ -549,11 +737,14 @@ public class ActiveServices { // and the service had previously asked to be told when // rebound, then do so. if (b.intent.apps.size() == 1 && b.intent.doRebind) { - requestServiceBindingLocked(s, b.intent, true); + requestServiceBindingLocked(s, b.intent, callerFg, true); } } else if (!b.intent.requested) { - requestServiceBindingLocked(s, b.intent, false); + requestServiceBindingLocked(s, b.intent, callerFg, false); } + + getServiceMap(s.userId).ensureNotStartingBackground(s); + } finally { Binder.restoreCallingIdentity(origId); } @@ -574,36 +765,32 @@ public class ActiveServices { b.binder = service; b.requested = true; b.received = true; - if (r.connections.size() > 0) { - Iterator<ArrayList<ConnectionRecord>> it - = r.connections.values().iterator(); - while (it.hasNext()) { - ArrayList<ConnectionRecord> clist = it.next(); - for (int i=0; i<clist.size(); i++) { - ConnectionRecord c = clist.get(i); - if (!filter.equals(c.binding.intent.intent)) { - if (DEBUG_SERVICE) Slog.v( - TAG, "Not publishing to: " + c); - if (DEBUG_SERVICE) Slog.v( - TAG, "Bound intent: " + c.binding.intent.intent); - if (DEBUG_SERVICE) Slog.v( - TAG, "Published intent: " + intent); - continue; - } - if (DEBUG_SERVICE) Slog.v(TAG, "Publishing to: " + c); - try { - c.conn.connected(r.name, service); - } catch (Exception e) { - Slog.w(TAG, "Failure sending service " + r.name + - " to connection " + c.conn.asBinder() + - " (in " + c.binding.client.processName + ")", e); - } + for (int conni=r.connections.size()-1; conni>=0; conni--) { + ArrayList<ConnectionRecord> clist = r.connections.valueAt(conni); + for (int i=0; i<clist.size(); i++) { + ConnectionRecord c = clist.get(i); + if (!filter.equals(c.binding.intent.intent)) { + if (DEBUG_SERVICE) Slog.v( + TAG, "Not publishing to: " + c); + if (DEBUG_SERVICE) Slog.v( + TAG, "Bound intent: " + c.binding.intent.intent); + if (DEBUG_SERVICE) Slog.v( + TAG, "Published intent: " + intent); + continue; + } + if (DEBUG_SERVICE) Slog.v(TAG, "Publishing to: " + c); + try { + c.conn.connected(r.name, service); + } catch (Exception e) { + Slog.w(TAG, "Failure sending service " + r.name + + " to connection " + c.conn.asBinder() + + " (in " + c.binding.client.processName + ")", e); } } } } - serviceDoneExecutingLocked(r, mStoppingServices.contains(r)); + serviceDoneExecutingLocked(r, mDestroyingServices.contains(r), false); } } finally { Binder.restoreCallingIdentity(origId); @@ -649,12 +836,21 @@ public class ActiveServices { + " at " + b + ": apps=" + (b != null ? b.apps.size() : 0)); - boolean inStopping = mStoppingServices.contains(r); + boolean inDestroying = mDestroyingServices.contains(r); if (b != null) { - if (b.apps.size() > 0 && !inStopping) { + if (b.apps.size() > 0 && !inDestroying) { // Applications have already bound since the last // unbind, so just rebind right here. - requestServiceBindingLocked(r, b, true); + boolean inFg = false; + for (int i=b.apps.size()-1; i>=0; i--) { + ProcessRecord client = b.apps.valueAt(i).client; + if (client != null && client.setSchedGroup + != Process.THREAD_GROUP_BG_NONINTERACTIVE) { + inFg = true; + break; + } + } + requestServiceBindingLocked(r, b, inFg, true); } else { // Note to tell the service the next time there is // a new client. @@ -662,7 +858,7 @@ public class ActiveServices { } } - serviceDoneExecutingLocked(r, inStopping); + serviceDoneExecutingLocked(r, inDestroying, false); } } finally { Binder.restoreCallingIdentity(origId); @@ -671,7 +867,7 @@ public class ActiveServices { private final ServiceRecord findServiceLocked(ComponentName name, IBinder token, int userId) { - ServiceRecord r = mServiceMap.getServiceByName(name, userId); + ServiceRecord r = getServiceByName(name, userId); return r == token ? r : null; } @@ -701,7 +897,7 @@ public class ActiveServices { private ServiceLookupResult retrieveServiceLocked(Intent service, String resolvedType, int callingPid, int callingUid, int userId, - boolean createIfNeeded) { + boolean createIfNeeded, boolean callingFromFg) { ServiceRecord r = null; if (DEBUG_SERVICE) Slog.v(TAG, "retrieveServiceLocked: " + service + " type=" + resolvedType + " callingUid=" + callingUid); @@ -709,12 +905,14 @@ public class ActiveServices { userId = mAm.handleIncomingUser(callingPid, callingUid, userId, false, true, "service", null); - if (service.getComponent() != null) { - r = mServiceMap.getServiceByName(service.getComponent(), userId); + ServiceMap smap = getServiceMap(userId); + final ComponentName comp = service.getComponent(); + if (comp != null) { + r = smap.mServicesByName.get(comp); } if (r == null) { Intent.FilterComparison filter = new Intent.FilterComparison(service); - r = mServiceMap.getServiceByIntent(filter, userId); + r = smap.mServicesByIntent.get(filter); } if (r == null) { try { @@ -735,14 +933,15 @@ public class ActiveServices { if (mAm.isSingleton(sInfo.processName, sInfo.applicationInfo, sInfo.name, sInfo.flags)) { userId = 0; + smap = getServiceMap(0); } sInfo = new ServiceInfo(sInfo); sInfo.applicationInfo = mAm.getAppInfoForUser(sInfo.applicationInfo, userId); } - r = mServiceMap.getServiceByName(name, userId); + r = smap.mServicesByName.get(name); if (r == null && createIfNeeded) { - Intent.FilterComparison filter = new Intent.FilterComparison( - service.cloneFilter()); + Intent.FilterComparison filter + = new Intent.FilterComparison(service.cloneFilter()); ServiceRestarter res = new ServiceRestarter(); BatteryStatsImpl.Uid.Pkg.Serv ss = null; BatteryStatsImpl stats = mAm.mBatteryStatsService.getActiveStatistics(); @@ -751,10 +950,10 @@ public class ActiveServices { sInfo.applicationInfo.uid, sInfo.packageName, sInfo.name); } - r = new ServiceRecord(mAm, ss, name, filter, sInfo, res); + r = new ServiceRecord(mAm, ss, name, filter, sInfo, callingFromFg, res); res.setService(r); - mServiceMap.putServiceByName(name, UserHandle.getUserId(r.appInfo.uid), r); - mServiceMap.putServiceByIntent(filter, UserHandle.getUserId(r.appInfo.uid), r); + smap.mServicesByName.put(name, r); + smap.mServicesByIntent.put(filter, r); // Make sure this component isn't in the pending list. int N = mPendingServices.size(); @@ -790,40 +989,55 @@ public class ActiveServices { + " requires " + r.permission); return new ServiceLookupResult(null, r.permission); } + if (!mAm.mIntentFirewall.checkService(r.name, service, callingUid, callingPid, + resolvedType, r.appInfo)) { + return null; + } return new ServiceLookupResult(r, null); } return null; } - private final void bumpServiceExecutingLocked(ServiceRecord r, String why) { - if (DEBUG_SERVICE) Log.v(TAG, ">>> EXECUTING " + private final void bumpServiceExecutingLocked(ServiceRecord r, boolean fg, String why) { + if (DEBUG_SERVICE) Slog.v(TAG, ">>> EXECUTING " + why + " of " + r + " in app " + r.app); - else if (DEBUG_SERVICE_EXECUTING) Log.v(TAG, ">>> EXECUTING " + else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG, ">>> EXECUTING " + why + " of " + r.shortName); long now = SystemClock.uptimeMillis(); - if (r.executeNesting == 0 && r.app != null) { - if (r.app.executingServices.size() == 0) { - Message msg = mAm.mHandler.obtainMessage( - ActivityManagerService.SERVICE_TIMEOUT_MSG); - msg.obj = r.app; - mAm.mHandler.sendMessageAtTime(msg, now+SERVICE_TIMEOUT); + if (r.executeNesting == 0) { + r.executeFg = fg; + ProcessStats.ServiceState stracker = r.getTracker(); + if (stracker != null) { + stracker.setExecuting(true, mAm.mProcessStats.getMemFactorLocked(), now); + } + if (r.app != null) { + r.app.executingServices.add(r); + r.app.execServicesFg |= fg; + if (r.app.executingServices.size() == 1) { + scheduleServiceTimeoutLocked(r.app); + } } - r.app.executingServices.add(r); + } else if (r.app != null && fg && !r.app.execServicesFg) { + r.app.execServicesFg = true; + scheduleServiceTimeoutLocked(r.app); } + r.executeFg |= fg; r.executeNesting++; r.executingStart = now; } private final boolean requestServiceBindingLocked(ServiceRecord r, - IntentBindRecord i, boolean rebind) { + IntentBindRecord i, boolean execInFg, boolean rebind) { if (r.app == null || r.app.thread == null) { // If service is not currently running, can't yet bind. return false; } if ((!i.requested || rebind) && i.apps.size() > 0) { try { - bumpServiceExecutingLocked(r, "bind"); - r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind); + bumpServiceExecutingLocked(r, execInFg, "bind"); + r.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE); + r.app.thread.scheduleBindService(r, i.intent.getIntent(), rebind, + r.app.repProcState); if (!rebind) { i.requested = true; } @@ -932,6 +1146,7 @@ public class ActiveServices { } if (!mRestartingServices.contains(r)) { + r.createdFromFg = false; mRestartingServices.add(r); } @@ -952,7 +1167,7 @@ public class ActiveServices { if (!mRestartingServices.contains(r)) { return; } - bringUpServiceLocked(r, r.intent.getIntent().getFlags(), true); + bringUpServiceLocked(r, r.intent.getIntent().getFlags(), r.createdFromFg, true); } private final boolean unscheduleServiceRestartLocked(ServiceRecord r) { @@ -966,12 +1181,12 @@ public class ActiveServices { } private final String bringUpServiceLocked(ServiceRecord r, - int intentFlags, boolean whileRestarting) { + int intentFlags, boolean execInFg, boolean whileRestarting) { //Slog.i(TAG, "Bring up service:"); //r.dump(" "); if (r.app != null && r.app.thread != null) { - sendServiceArgsLocked(r, false); + sendServiceArgsLocked(r, execInFg, false); return null; } @@ -986,6 +1201,13 @@ public class ActiveServices { // restarting state. mRestartingServices.remove(r); + // Make sure this service is no longer considered delayed, we are starting it now. + if (r.delayed) { + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (bring up): " + r); + getServiceMap(r.userId).mDelayedStartList.remove(r); + r.delayed = false; + } + // Make sure that the user who owns this service is started. If not, // we don't want to allow it to run. if (mAm.mStartedUsers.get(r.userId) == null) { @@ -994,7 +1216,7 @@ public class ActiveServices { + r.appInfo.uid + " for service " + r.intent.getIntent() + ": user " + r.userId + " is stopped"; Slog.w(TAG, msg); - bringDownServiceLocked(r, true); + bringDownServiceLocked(r); return msg; } @@ -1013,13 +1235,13 @@ public class ActiveServices { ProcessRecord app; if (!isolated) { - app = mAm.getProcessRecordLocked(procName, r.appInfo.uid); - if (DEBUG_MU) - Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid + " app=" + app); + app = mAm.getProcessRecordLocked(procName, r.appInfo.uid, false); + if (DEBUG_MU) Slog.v(TAG_MU, "bringUpServiceLocked: appInfo.uid=" + r.appInfo.uid + + " app=" + app); if (app != null && app.thread != null) { try { - app.addPackage(r.appInfo.packageName); - realStartServiceLocked(r, app); + app.addPackage(r.appInfo.packageName, mAm.mProcessStats); + realStartServiceLocked(r, app, execInFg); return null; } catch (RemoteException e) { Slog.w(TAG, "Exception when starting service " + r.shortName, e); @@ -1042,13 +1264,13 @@ public class ActiveServices { // to be executed when the app comes up. if (app == null) { if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags, - "service", r.name, false, isolated)) == null) { + "service", r.name, false, isolated, false)) == null) { String msg = "Unable to launch app " + r.appInfo.packageName + "/" + r.appInfo.uid + " for service " + r.intent.getIntent() + ": process is bad"; Slog.w(TAG, msg); - bringDownServiceLocked(r, true); + bringDownServiceLocked(r); return msg; } if (isolated) { @@ -1060,21 +1282,29 @@ public class ActiveServices { mPendingServices.add(r); } + if (r.delayedStop) { + // Oh and hey we've already been asked to stop! + r.delayedStop = false; + if (r.startRequested) { + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Applying delayed stop (in bring up): " + r); + stopServiceLocked(r); + } + } + return null; } - private final void requestServiceBindingsLocked(ServiceRecord r) { - Iterator<IntentBindRecord> bindings = r.bindings.values().iterator(); - while (bindings.hasNext()) { - IntentBindRecord i = bindings.next(); - if (!requestServiceBindingLocked(r, i, false)) { + private final void requestServiceBindingsLocked(ServiceRecord r, boolean execInFg) { + for (int i=r.bindings.size()-1; i>=0; i--) { + IntentBindRecord ibr = r.bindings.valueAt(i); + if (!requestServiceBindingLocked(r, ibr, execInFg, false)) { break; } } } private final void realStartServiceLocked(ServiceRecord r, - ProcessRecord app) throws RemoteException { + ProcessRecord app, boolean execInFg) throws RemoteException { if (app.thread == null) { throw new RemoteException(); } @@ -1085,19 +1315,24 @@ public class ActiveServices { r.restartTime = r.lastActivity = SystemClock.uptimeMillis(); app.services.add(r); - bumpServiceExecutingLocked(r, "create"); - mAm.updateLruProcessLocked(app, true); + bumpServiceExecutingLocked(r, execInFg, "create"); + mAm.updateLruProcessLocked(app, true, false); boolean created = false; try { + String nameTerm; + int lastPeriod = r.shortName.lastIndexOf('.'); + nameTerm = lastPeriod >= 0 ? r.shortName.substring(lastPeriod) : r.shortName; EventLogTags.writeAmCreateService( - r.userId, System.identityHashCode(r), r.shortName, r.app.pid); + r.userId, System.identityHashCode(r), nameTerm, r.app.uid, r.app.pid); synchronized (r.stats.getBatteryStats()) { r.stats.startLaunchedLocked(); } mAm.ensurePackageDexOpt(r.serviceInfo.packageName); + app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_SERVICE); app.thread.scheduleCreateService(r, r.serviceInfo, - mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo)); + mAm.compatibilityInfoForPackageLocked(r.serviceInfo.applicationInfo), + app.repProcState); r.postNotification(); created = true; } finally { @@ -1107,7 +1342,7 @@ public class ActiveServices { } } - requestServiceBindingsLocked(r); + requestServiceBindingsLocked(r, execInFg); // If the service is in the started state, and there are no // pending arguments, then fake up one so its onStartCommand() will @@ -1117,10 +1352,25 @@ public class ActiveServices { null, null)); } - sendServiceArgsLocked(r, true); + sendServiceArgsLocked(r, execInFg, true); + + if (r.delayed) { + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "REM FR DELAY LIST (new proc): " + r); + getServiceMap(r.userId).mDelayedStartList.remove(r); + r.delayed = false; + } + + if (r.delayedStop) { + // Oh and hey we've already been asked to stop! + r.delayedStop = false; + if (r.startRequested) { + if (DEBUG_DELAYED_STATS) Slog.v(TAG, "Applying delayed stop (from start): " + r); + stopServiceLocked(r); + } + } } - private final void sendServiceArgsLocked(ServiceRecord r, + private final void sendServiceArgsLocked(ServiceRecord r, boolean execInFg, boolean oomAdjusted) { final int N = r.pendingStarts.size(); if (N == 0) { @@ -1146,7 +1396,7 @@ public class ActiveServices { mAm.grantUriPermissionUncheckedFromIntentLocked(si.neededGrants, si.getUriPermissionsLocked()); } - bumpServiceExecutingLocked(r, "start"); + bumpServiceExecutingLocked(r, execInFg, "start"); if (!oomAdjusted) { oomAdjusted = true; mAm.updateOomAdjLocked(r.app); @@ -1171,60 +1421,72 @@ public class ActiveServices { } } - private final void bringDownServiceLocked(ServiceRecord r, boolean force) { + private final boolean isServiceNeeded(ServiceRecord r, boolean knowConn, boolean hasConn) { + // Are we still explicitly being asked to run? + if (r.startRequested) { + return true; + } + + // Is someone still bound to us keepign us running? + if (!knowConn) { + hasConn = r.hasAutoCreateConnections(); + } + if (hasConn) { + return true; + } + + return false; + } + + private final void bringDownServiceIfNeededLocked(ServiceRecord r, boolean knowConn, + boolean hasConn) { //Slog.i(TAG, "Bring down service:"); //r.dump(" "); - // Does it still need to run? - if (!force && r.startRequested) { + if (isServiceNeeded(r, knowConn, hasConn)) { return; } - if (r.connections.size() > 0) { - if (!force) { - // XXX should probably keep a count of the number of auto-create - // connections directly in the service. - Iterator<ArrayList<ConnectionRecord>> it = r.connections.values().iterator(); - while (it.hasNext()) { - ArrayList<ConnectionRecord> cr = it.next(); - for (int i=0; i<cr.size(); i++) { - if ((cr.get(i).flags&Context.BIND_AUTO_CREATE) != 0) { - return; - } - } - } - } - // Report to all of the connections that the service is no longer - // available. - Iterator<ArrayList<ConnectionRecord>> it = r.connections.values().iterator(); - while (it.hasNext()) { - ArrayList<ConnectionRecord> c = it.next(); - for (int i=0; i<c.size(); i++) { - ConnectionRecord cr = c.get(i); - // There is still a connection to the service that is - // being brought down. Mark it as dead. - cr.serviceDead = true; - try { - cr.conn.connected(r.name, null); - } catch (Exception e) { - Slog.w(TAG, "Failure disconnecting service " + r.name + - " to connection " + c.get(i).conn.asBinder() + - " (in " + c.get(i).binding.client.processName + ")", e); - } + // Are we in the process of launching? + if (mPendingServices.contains(r)) { + return; + } + + bringDownServiceLocked(r); + } + + private final void bringDownServiceLocked(ServiceRecord r) { + //Slog.i(TAG, "Bring down service:"); + //r.dump(" "); + + // Report to all of the connections that the service is no longer + // available. + for (int conni=r.connections.size()-1; conni>=0; conni--) { + ArrayList<ConnectionRecord> c = r.connections.valueAt(conni); + for (int i=0; i<c.size(); i++) { + ConnectionRecord cr = c.get(i); + // There is still a connection to the service that is + // being brought down. Mark it as dead. + cr.serviceDead = true; + try { + cr.conn.connected(r.name, null); + } catch (Exception e) { + Slog.w(TAG, "Failure disconnecting service " + r.name + + " to connection " + c.get(i).conn.asBinder() + + " (in " + c.get(i).binding.client.processName + ")", e); } } } // Tell the service that it has been unbound. - if (r.bindings.size() > 0 && r.app != null && r.app.thread != null) { - Iterator<IntentBindRecord> it = r.bindings.values().iterator(); - while (it.hasNext()) { - IntentBindRecord ibr = it.next(); + if (r.app != null && r.app.thread != null) { + for (int i=r.bindings.size()-1; i>=0; i--) { + IntentBindRecord ibr = r.bindings.valueAt(i); if (DEBUG_SERVICE) Slog.v(TAG, "Bringing down binding " + ibr + ": hasBound=" + ibr.hasBound); - if (r.app != null && r.app.thread != null && ibr.hasBound) { + if (ibr.hasBound) { try { - bumpServiceExecutingLocked(r, "bring down unbind"); + bumpServiceExecutingLocked(r, false, "bring down unbind"); mAm.updateOomAdjLocked(r.app); ibr.hasBound = false; r.app.thread.scheduleUnbindService(r, @@ -1232,7 +1494,7 @@ public class ActiveServices { } catch (Exception e) { Slog.w(TAG, "Exception when unbinding service " + r.shortName, e); - serviceDoneExecutingLocked(r, true); + serviceProcessGoneLocked(r); } } } @@ -1242,8 +1504,9 @@ public class ActiveServices { EventLogTags.writeAmDestroyService( r.userId, System.identityHashCode(r), (r.app != null) ? r.app.pid : -1); - mServiceMap.removeServiceByName(r.name, r.userId); - mServiceMap.removeServiceByIntent(r.intent, r.userId); + final ServiceMap smap = getServiceMap(r.userId); + smap.mServicesByName.remove(r.name); + smap.mServicesByIntent.remove(r.intent); r.totalRestartCount = 0; unscheduleServiceRestartLocked(r); @@ -1274,14 +1537,14 @@ public class ActiveServices { r.app.services.remove(r); if (r.app.thread != null) { try { - bumpServiceExecutingLocked(r, "stop"); - mStoppingServices.add(r); + bumpServiceExecutingLocked(r, false, "destroy"); + mDestroyingServices.add(r); mAm.updateOomAdjLocked(r.app); r.app.thread.scheduleStopService(r); } catch (Exception e) { - Slog.w(TAG, "Exception when stopping service " + Slog.w(TAG, "Exception when destroying service " + r.shortName, e); - serviceDoneExecutingLocked(r, true); + serviceProcessGoneLocked(r); } updateServiceForegroundLocked(r.app, false); } else { @@ -1300,6 +1563,19 @@ public class ActiveServices { if (r.restarter instanceof ServiceRestarter) { ((ServiceRestarter)r.restarter).setService(null); } + + int memFactor = mAm.mProcessStats.getMemFactorLocked(); + long now = SystemClock.uptimeMillis(); + if (r.tracker != null) { + r.tracker.setStarted(false, memFactor, now); + r.tracker.setBound(false, memFactor, now); + if (r.executeNesting == 0) { + r.tracker.clearCurrentOwner(r, false); + r.tracker = null; + } + } + + smap.ensureNotStartingBackground(r); } void removeConnectionLocked( @@ -1344,7 +1620,7 @@ public class ActiveServices { if (s.app != null && s.app.thread != null && b.intent.apps.size() == 0 && b.intent.hasBound) { try { - bumpServiceExecutingLocked(s, "unbind"); + bumpServiceExecutingLocked(s, false, "unbind"); mAm.updateOomAdjLocked(s.app); b.intent.hasBound = false; // Assume the client doesn't want to know about a rebind; @@ -1353,18 +1629,25 @@ public class ActiveServices { s.app.thread.scheduleUnbindService(s, b.intent.intent.getIntent()); } catch (Exception e) { Slog.w(TAG, "Exception when unbinding service " + s.shortName, e); - serviceDoneExecutingLocked(s, true); + serviceProcessGoneLocked(s); } } if ((c.flags&Context.BIND_AUTO_CREATE) != 0) { - bringDownServiceLocked(s, false); + boolean hasAutoCreate = s.hasAutoCreateConnections(); + if (!hasAutoCreate) { + if (s.tracker != null) { + s.tracker.setBound(false, mAm.mProcessStats.getMemFactorLocked(), + SystemClock.uptimeMillis()); + } + } + bringDownServiceIfNeededLocked(s, true, hasAutoCreate); } } } void serviceDoneExecutingLocked(ServiceRecord r, int type, int startId, int res) { - boolean inStopping = mStoppingServices.contains(r); + boolean inDestroying = mDestroyingServices.contains(r); if (r != null) { if (type == 1) { // This is a call from a service start... take care of @@ -1417,7 +1700,7 @@ public class ActiveServices { } } final long origId = Binder.clearCallingIdentity(); - serviceDoneExecutingLocked(r, inStopping); + serviceDoneExecutingLocked(r, inDestroying, inDestroying); Binder.restoreCallingIdentity(origId); } else { Slog.w(TAG, "Done executing unknown service from pid " @@ -1425,28 +1708,59 @@ public class ActiveServices { } } - private void serviceDoneExecutingLocked(ServiceRecord r, boolean inStopping) { + private void serviceProcessGoneLocked(ServiceRecord r) { + if (r.tracker != null) { + int memFactor = mAm.mProcessStats.getMemFactorLocked(); + long now = SystemClock.uptimeMillis(); + r.tracker.setExecuting(false, memFactor, now); + r.tracker.setBound(false, memFactor, now); + } + serviceDoneExecutingLocked(r, true, true); + } + + private void serviceDoneExecutingLocked(ServiceRecord r, boolean inDestroying, + boolean finishing) { if (DEBUG_SERVICE) Slog.v(TAG, "<<< DONE EXECUTING " + r + ": nesting=" + r.executeNesting - + ", inStopping=" + inStopping + ", app=" + r.app); + + ", inDestroying=" + inDestroying + ", app=" + r.app); else if (DEBUG_SERVICE_EXECUTING) Slog.v(TAG, "<<< DONE EXECUTING " + r.shortName); r.executeNesting--; - if (r.executeNesting <= 0 && r.app != null) { - if (DEBUG_SERVICE) Slog.v(TAG, - "Nesting at 0 of " + r.shortName); - r.app.executingServices.remove(r); - if (r.app.executingServices.size() == 0) { - if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG, - "No more executingServices of " + r.shortName); - mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); - } - if (inStopping) { + if (r.executeNesting <= 0) { + if (r.app != null) { if (DEBUG_SERVICE) Slog.v(TAG, - "doneExecuting remove stopping " + r); - mStoppingServices.remove(r); - r.bindings.clear(); + "Nesting at 0 of " + r.shortName); + r.app.execServicesFg = false; + r.app.executingServices.remove(r); + if (r.app.executingServices.size() == 0) { + if (DEBUG_SERVICE || DEBUG_SERVICE_EXECUTING) Slog.v(TAG, + "No more executingServices of " + r.shortName); + mAm.mHandler.removeMessages(ActivityManagerService.SERVICE_TIMEOUT_MSG, r.app); + } else if (r.executeFg) { + // Need to re-evaluate whether the app still needs to be in the foreground. + for (int i=r.app.executingServices.size()-1; i>=0; i--) { + if (r.app.executingServices.valueAt(i).executeFg) { + r.app.execServicesFg = true; + break; + } + } + } + if (inDestroying) { + if (DEBUG_SERVICE) Slog.v(TAG, + "doneExecuting remove destroying " + r); + mDestroyingServices.remove(r); + r.bindings.clear(); + } + mAm.updateOomAdjLocked(r.app); + } + r.executeFg = false; + if (r.tracker != null) { + r.tracker.setExecuting(false, mAm.mProcessStats.getMemFactorLocked(), + SystemClock.uptimeMillis()); + if (finishing) { + r.tracker.clearCurrentOwner(r, false); + r.tracker = null; + } } - mAm.updateOomAdjLocked(r.app); } } @@ -1465,7 +1779,8 @@ public class ActiveServices { mPendingServices.remove(i); i--; - realStartServiceLocked(sr, proc); + proc.addPackage(sr.appInfo.packageName, mAm.mProcessStats); + realStartServiceLocked(sr, proc, sr.createdFromFg); didSomething = true; } } catch (Exception e) { @@ -1503,17 +1818,18 @@ public class ActiveServices { sr.isolatedProc = null; mPendingServices.remove(i); i--; - bringDownServiceLocked(sr, true); + bringDownServiceLocked(sr); } } } private boolean collectForceStopServicesLocked(String name, int userId, boolean evenPersistent, boolean doit, - HashMap<ComponentName, ServiceRecord> services, + ArrayMap<ComponentName, ServiceRecord> services, ArrayList<ServiceRecord> result) { boolean didSomething = false; - for (ServiceRecord service : services.values()) { + for (int i=0; i<services.size(); i++) { + ServiceRecord service = services.valueAt(i); if ((name == null || service.packageName.equals(name)) && (service.app == null || evenPersistent || !service.app.persistent)) { if (!doit) { @@ -1536,17 +1852,17 @@ public class ActiveServices { boolean didSomething = false; ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); if (userId == UserHandle.USER_ALL) { - for (int i=0; i<mServiceMap.mServicesByNamePerUser.size(); i++) { + for (int i=0; i<mServiceMap.size(); i++) { didSomething |= collectForceStopServicesLocked(name, userId, evenPersistent, - doit, mServiceMap.mServicesByNamePerUser.valueAt(i), services); + doit, mServiceMap.valueAt(i).mServicesByName, services); if (!doit && didSomething) { return true; } } } else { - HashMap<ComponentName, ServiceRecord> items - = mServiceMap.mServicesByNamePerUser.get(userId); - if (items != null) { + ServiceMap smap = mServiceMap.get(userId); + if (smap != null) { + ArrayMap<ComponentName, ServiceRecord> items = smap.mServicesByName; didSomething = collectForceStopServicesLocked(name, userId, evenPersistent, doit, items, services); } @@ -1554,14 +1870,16 @@ public class ActiveServices { int N = services.size(); for (int i=0; i<N; i++) { - bringDownServiceLocked(services.get(i), true); + bringDownServiceLocked(services.get(i)); } return didSomething; } void cleanUpRemovedTaskLocked(TaskRecord tr, ComponentName component, Intent baseIntent) { ArrayList<ServiceRecord> services = new ArrayList<ServiceRecord>(); - for (ServiceRecord sr : mServiceMap.getAllServices(tr.userId)) { + ArrayMap<ComponentName, ServiceRecord> alls = getServices(tr.userId); + for (int i=0; i<alls.size(); i++) { + ServiceRecord sr = alls.valueAt(i); if (sr.packageName.equals(component.getPackageName())) { services.add(sr); } @@ -1578,7 +1896,9 @@ public class ActiveServices { sr.pendingStarts.add(new ServiceRecord.StartItem(sr, true, sr.makeNextStartId(), baseIntent, null)); if (sr.app != null && sr.app.thread != null) { - sendServiceArgsLocked(sr, false); + // We always run in the foreground, since this is called as + // part of the "remove task" UI operation. + sendServiceArgsLocked(sr, true, false); } } } @@ -1595,22 +1915,18 @@ public class ActiveServices { Iterator<ServiceRecord> it = app.services.iterator(); while (it.hasNext()) { ServiceRecord r = it.next(); - if (r.connections.size() > 0) { - Iterator<ArrayList<ConnectionRecord>> jt - = r.connections.values().iterator(); - while (jt.hasNext()) { - ArrayList<ConnectionRecord> cl = jt.next(); - for (int i=0; i<cl.size(); i++) { - ConnectionRecord c = cl.get(i); - if (c.binding.client != app) { - try { - //c.conn.connected(r.className, null); - } catch (Exception e) { - // todo: this should be asynchronous! - Slog.w(TAG, "Exception thrown disconnected servce " - + r.shortName - + " from app " + app.processName, e); - } + for (int conni=r.connections.size()-1; conni>=0; conni--) { + ArrayList<ConnectionRecord> cl = r.connections.valueAt(conni); + for (int i=0; i<cl.size(); i++) { + ConnectionRecord c = cl.get(i); + if (c.binding.client != app) { + try { + //c.conn.connected(r.className, null); + } catch (Exception e) { + // todo: this should be asynchronous! + Slog.w(TAG, "Exception thrown disconnected servce " + + r.shortName + + " from app " + app.processName, e); } } } @@ -1620,84 +1936,80 @@ public class ActiveServices { } // Clean up any connections this application has to other services. - if (app.connections.size() > 0) { - Iterator<ConnectionRecord> it = app.connections.iterator(); - while (it.hasNext()) { - ConnectionRecord r = it.next(); - removeConnectionLocked(r, app, null); - } + for (int i=app.connections.size()-1; i>=0; i--) { + ConnectionRecord r = app.connections.valueAt(i); + removeConnectionLocked(r, app, null); } app.connections.clear(); - if (app.services.size() != 0) { + for (int i=app.services.size()-1; i>=0; i--) { // Any services running in the application need to be placed // back in the pending list. - Iterator<ServiceRecord> it = app.services.iterator(); - while (it.hasNext()) { - ServiceRecord sr = it.next(); - synchronized (sr.stats.getBatteryStats()) { - sr.stats.stopLaunchedLocked(); - } - sr.app = null; - sr.isolatedProc = null; - sr.executeNesting = 0; - if (mStoppingServices.remove(sr)) { - if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove stopping " + sr); - } - - boolean hasClients = sr.bindings.size() > 0; - if (hasClients) { - Iterator<IntentBindRecord> bindings - = sr.bindings.values().iterator(); - while (bindings.hasNext()) { - IntentBindRecord b = bindings.next(); - if (DEBUG_SERVICE) Slog.v(TAG, "Killing binding " + b - + ": shouldUnbind=" + b.hasBound); - b.binder = null; - b.requested = b.received = b.hasBound = false; - } - } - - if (sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags - &ApplicationInfo.FLAG_PERSISTENT) == 0) { - Slog.w(TAG, "Service crashed " + sr.crashCount - + " times, stopping: " + sr); - EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH, - sr.userId, sr.crashCount, sr.shortName, app.pid); - bringDownServiceLocked(sr, true); - } else if (!allowRestart) { - bringDownServiceLocked(sr, true); - } else { - 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 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); - } + ServiceRecord sr = app.services.valueAt(i); + synchronized (sr.stats.getBatteryStats()) { + sr.stats.stopLaunchedLocked(); + } + sr.app = null; + sr.isolatedProc = null; + sr.executeNesting = 0; + sr.forceClearTracker(); + if (mDestroyingServices.remove(sr)) { + if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove destroying " + sr); + } + + final int numClients = sr.bindings.size(); + for (int bindingi=numClients-1; bindingi>=0; bindingi--) { + IntentBindRecord b = sr.bindings.valueAt(bindingi); + if (DEBUG_SERVICE) Slog.v(TAG, "Killing binding " + b + + ": shouldUnbind=" + b.hasBound); + b.binder = null; + b.requested = b.received = b.hasBound = false; + } + + if (sr.crashCount >= 2 && (sr.serviceInfo.applicationInfo.flags + &ApplicationInfo.FLAG_PERSISTENT) == 0) { + Slog.w(TAG, "Service crashed " + sr.crashCount + + " times, stopping: " + sr); + EventLog.writeEvent(EventLogTags.AM_SERVICE_CRASHED_TOO_MUCH, + sr.userId, sr.crashCount, sr.shortName, app.pid); + bringDownServiceLocked(sr); + } else if (!allowRestart) { + bringDownServiceLocked(sr); + } else { + 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 we also will stop it here. + if (sr.startRequested && (sr.stopIfKilled || canceled)) { + if (sr.pendingStarts.size() == 0) { + sr.startRequested = false; + if (sr.tracker != null) { + sr.tracker.setStarted(false, mAm.mProcessStats.getMemFactorLocked(), + SystemClock.uptimeMillis()); + } + if (!sr.hasAutoCreateConnections()) { + // Whoops, no reason to restart! + bringDownServiceLocked(sr); } } } } + } - if (!allowRestart) { - app.services.clear(); - } + if (!allowRestart) { + app.services.clear(); } // Make sure we have no more records on the stopping list. - int i = mStoppingServices.size(); + int i = mDestroyingServices.size(); while (i > 0) { i--; - ServiceRecord sr = mStoppingServices.get(i); + ServiceRecord sr = mDestroyingServices.get(i); if (sr.app == app) { - mStoppingServices.remove(i); - if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove stopping " + sr); + sr.forceClearTracker(); + mDestroyingServices.remove(i); + if (DEBUG_SERVICE) Slog.v(TAG, "killServices remove destroying " + sr); } } @@ -1732,7 +2044,8 @@ public class ActiveServices { info.flags |= ActivityManager.RunningServiceInfo.FLAG_PERSISTENT_PROCESS; } - for (ArrayList<ConnectionRecord> connl : r.connections.values()) { + for (int conni=r.connections.size()-1; conni>=0; conni--) { + ArrayList<ConnectionRecord> connl = r.connections.valueAt(conni); for (int i=0; i<connl.size(); i++) { ConnectionRecord conn = connl.get(i); if (conn.clientLabel != 0) { @@ -1758,12 +2071,10 @@ public class ActiveServices { uid) == PackageManager.PERMISSION_GRANTED) { int[] users = mAm.getUsersLocked(); for (int ui=0; ui<users.length && res.size() < maxNum; ui++) { - if (mServiceMap.getAllServices(users[ui]).size() > 0) { - Iterator<ServiceRecord> it = mServiceMap.getAllServices( - users[ui]).iterator(); - while (it.hasNext() && res.size() < maxNum) { - res.add(makeRunningServiceInfoLocked(it.next())); - } + ArrayMap<ComponentName, ServiceRecord> alls = getServices(users[ui]); + for (int i=0; i<alls.size() && res.size() < maxNum; i++) { + ServiceRecord sr = alls.valueAt(i); + res.add(makeRunningServiceInfoLocked(sr)); } } @@ -1776,12 +2087,10 @@ public class ActiveServices { } } else { int userId = UserHandle.getUserId(uid); - if (mServiceMap.getAllServices(userId).size() > 0) { - Iterator<ServiceRecord> it - = mServiceMap.getAllServices(userId).iterator(); - while (it.hasNext() && res.size() < maxNum) { - res.add(makeRunningServiceInfoLocked(it.next())); - } + ArrayMap<ComponentName, ServiceRecord> alls = getServices(userId); + for (int i=0; i<alls.size() && res.size() < maxNum; i++) { + ServiceRecord sr = alls.valueAt(i); + res.add(makeRunningServiceInfoLocked(sr)); } for (int i=0; i<mRestartingServices.size() && res.size() < maxNum; i++) { @@ -1803,9 +2112,10 @@ public class ActiveServices { public PendingIntent getRunningServiceControlPanelLocked(ComponentName name) { int userId = UserHandle.getUserId(Binder.getCallingUid()); - ServiceRecord r = mServiceMap.getServiceByName(name, userId); + ServiceRecord r = getServiceByName(name, userId); if (r != null) { - for (ArrayList<ConnectionRecord> conn : r.connections.values()) { + for (int conni=r.connections.size()-1; conni>=0; conni--) { + ArrayList<ConnectionRecord> conn = r.connections.valueAt(conni); for (int i=0; i<conn.size(); i++) { if (conn.get(i).clientIntent != null) { return conn.get(i).clientIntent; @@ -1823,12 +2133,12 @@ public class ActiveServices { if (proc.executingServices.size() == 0 || proc.thread == null) { return; } - long maxTime = SystemClock.uptimeMillis() - SERVICE_TIMEOUT; - Iterator<ServiceRecord> it = proc.executingServices.iterator(); + long maxTime = SystemClock.uptimeMillis() - + (proc.execServicesFg ? SERVICE_TIMEOUT : SERVICE_BACKGROUND_TIMEOUT); ServiceRecord timeout = null; long nextTime = 0; - while (it.hasNext()) { - ServiceRecord sr = it.next(); + for (int i=proc.executingServices.size()-1; i>=0; i--) { + ServiceRecord sr = proc.executingServices.valueAt(i); if (sr.executingStart < maxTime) { timeout = sr; break; @@ -1844,7 +2154,8 @@ public class ActiveServices { Message msg = mAm.mHandler.obtainMessage( ActivityManagerService.SERVICE_TIMEOUT_MSG); msg.obj = proc; - mAm.mHandler.sendMessageAtTime(msg, nextTime+SERVICE_TIMEOUT); + mAm.mHandler.sendMessageAtTime(msg, proc.execServicesFg + ? (nextTime+SERVICE_TIMEOUT) : (nextTime + SERVICE_BACKGROUND_TIMEOUT)); } } @@ -1853,12 +2164,25 @@ public class ActiveServices { } } + void scheduleServiceTimeoutLocked(ProcessRecord proc) { + if (proc.executingServices.size() == 0 || proc.thread == null) { + return; + } + long now = SystemClock.uptimeMillis(); + Message msg = mAm.mHandler.obtainMessage( + ActivityManagerService.SERVICE_TIMEOUT_MSG); + msg.obj = proc; + mAm.mHandler.sendMessageAtTime(msg, + proc.execServicesFg ? (now+SERVICE_TIMEOUT) : (now+ SERVICE_BACKGROUND_TIMEOUT)); + } + /** * Prints a list of ServiceRecords (dumpsys activity services) */ - boolean dumpServicesLocked(FileDescriptor fd, PrintWriter pw, String[] args, + void dumpServicesLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) { boolean needSep = false; + boolean printedAnything = false; ItemMatcher matcher = new ItemMatcher(); matcher.build(args, opti); @@ -1867,14 +2191,13 @@ public class ActiveServices { try { int[] users = mAm.getUsersLocked(); for (int user : users) { - if (mServiceMap.getAllServices(user).size() > 0) { - boolean printed = false; + ServiceMap smap = getServiceMap(user); + boolean printed = false; + if (smap.mServicesByName.size() > 0) { long nowReal = SystemClock.elapsedRealtime(); - Iterator<ServiceRecord> it = mServiceMap.getAllServices( - user).iterator(); needSep = false; - while (it.hasNext()) { - ServiceRecord r = it.next(); + for (int si=0; si<smap.mServicesByName.size(); si++) { + ServiceRecord r = smap.mServicesByName.valueAt(si); if (!matcher.match(r, r.name)) { continue; } @@ -1882,12 +2205,13 @@ public class ActiveServices { continue; } if (!printed) { - if (user != 0) { + if (printedAnything) { pw.println(); } pw.println(" User " + user + " active services:"); printed = true; } + printedAnything = true; if (needSep) { pw.println(); } @@ -1907,7 +2231,8 @@ public class ActiveServices { pw.println(r.connections.size()); if (r.connections.size() > 0) { pw.println(" Connections:"); - for (ArrayList<ConnectionRecord> clist : r.connections.values()) { + for (int conni=0; conni<r.connections.size(); conni++) { + ArrayList<ConnectionRecord> clist = r.connections.valueAt(conni); for (int i = 0; i < clist.size(); i++) { ConnectionRecord conn = clist.get(i); pw.print(" "); @@ -1943,11 +2268,49 @@ public class ActiveServices { needSep = true; } } - needSep = printed; + needSep |= printed; + } + printed = false; + for (int si=0, SN=smap.mDelayedStartList.size(); si<SN; si++) { + ServiceRecord r = smap.mDelayedStartList.get(si); + if (!matcher.match(r, r.name)) { + continue; + } + if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { + continue; + } + if (!printed) { + if (printedAnything) { + pw.println(); + } + pw.println(" User " + user + " delayed start services:"); + printed = true; + } + printedAnything = true; + pw.print(" * Delayed start "); pw.println(r); + } + printed = false; + for (int si=0, SN=smap.mStartingBackground.size(); si<SN; si++) { + ServiceRecord r = smap.mStartingBackground.get(si); + if (!matcher.match(r, r.name)) { + continue; + } + if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { + continue; + } + if (!printed) { + if (printedAnything) { + pw.println(); + } + pw.println(" User " + user + " starting in background:"); + printed = true; + } + printedAnything = true; + pw.print(" * Starting bg "); pw.println(r); } } } catch (Exception e) { - Log.w(TAG, "Exception in dumpServicesLocked: " + e); + Slog.w(TAG, "Exception in dumpServicesLocked", e); } if (mPendingServices.size() > 0) { @@ -1960,8 +2323,9 @@ public class ActiveServices { if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { continue; } + printedAnything = true; if (!printed) { - if (needSep) pw.println(" "); + if (needSep) pw.println(); needSep = true; pw.println(" Pending services:"); printed = true; @@ -1982,8 +2346,9 @@ public class ActiveServices { if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { continue; } + printedAnything = true; if (!printed) { - if (needSep) pw.println(" "); + if (needSep) pw.println(); needSep = true; pw.println(" Restarting services:"); printed = true; @@ -1994,59 +2359,58 @@ public class ActiveServices { needSep = true; } - if (mStoppingServices.size() > 0) { + if (mDestroyingServices.size() > 0) { boolean printed = false; - for (int i=0; i<mStoppingServices.size(); i++) { - ServiceRecord r = mStoppingServices.get(i); + for (int i=0; i< mDestroyingServices.size(); i++) { + ServiceRecord r = mDestroyingServices.get(i); if (!matcher.match(r, r.name)) { continue; } if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { continue; } + printedAnything = true; if (!printed) { - if (needSep) pw.println(" "); + if (needSep) pw.println(); needSep = true; - pw.println(" Stopping services:"); + pw.println(" Destroying services:"); printed = true; } - pw.print(" * Stopping "); pw.println(r); + pw.print(" * Destroy "); pw.println(r); r.dump(pw, " "); } needSep = true; } if (dumpAll) { - if (mServiceConnections.size() > 0) { - boolean printed = false; - Iterator<ArrayList<ConnectionRecord>> it - = mServiceConnections.values().iterator(); - while (it.hasNext()) { - ArrayList<ConnectionRecord> r = it.next(); - for (int i=0; i<r.size(); i++) { - ConnectionRecord cr = r.get(i); - if (!matcher.match(cr.binding.service, cr.binding.service.name)) { - continue; - } - if (dumpPackage != null && (cr.binding.client == null - || !dumpPackage.equals(cr.binding.client.info.packageName))) { - continue; - } - if (!printed) { - if (needSep) pw.println(" "); - needSep = true; - pw.println(" Connection bindings to services:"); - printed = true; - } - pw.print(" * "); pw.println(cr); - cr.dump(pw, " "); + boolean printed = false; + for (int ic=0; ic<mServiceConnections.size(); ic++) { + ArrayList<ConnectionRecord> r = mServiceConnections.valueAt(ic); + for (int i=0; i<r.size(); i++) { + ConnectionRecord cr = r.get(i); + if (!matcher.match(cr.binding.service, cr.binding.service.name)) { + continue; + } + if (dumpPackage != null && (cr.binding.client == null + || !dumpPackage.equals(cr.binding.client.info.packageName))) { + continue; } + printedAnything = true; + if (!printed) { + if (needSep) pw.println(); + needSep = true; + pw.println(" Connection bindings to services:"); + printed = true; + } + pw.print(" * "); pw.println(cr); + cr.dump(pw, " "); } - needSep = true; } } - return needSep; + if (!printedAnything) { + pw.println(" (nothing)"); + } } /** @@ -2065,7 +2429,13 @@ public class ActiveServices { int[] users = mAm.getUsersLocked(); if ("all".equals(name)) { for (int user : users) { - for (ServiceRecord r1 : mServiceMap.getAllServices(user)) { + ServiceMap smap = mServiceMap.get(user); + if (smap == null) { + continue; + } + ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName; + for (int i=0; i<alls.size(); i++) { + ServiceRecord r1 = alls.valueAt(i); services.add(r1); } } @@ -2084,7 +2454,13 @@ public class ActiveServices { } for (int user : users) { - for (ServiceRecord r1 : mServiceMap.getAllServices(user)) { + ServiceMap smap = mServiceMap.get(user); + if (smap == null) { + continue; + } + ArrayMap<ComponentName, ServiceRecord> alls = smap.mServicesByName; + for (int i=0; i<alls.size(); i++) { + ServiceRecord r1 = alls.valueAt(i); if (componentName != null) { if (r1.name.equals(componentName)) { services.add(r1); diff --git a/services/java/com/android/server/am/ActivityManagerService.java b/services/java/com/android/server/am/ActivityManagerService.java index 6fccee067f00..cc5a0b79cac3 100644 --- a/services/java/com/android/server/am/ActivityManagerService.java +++ b/services/java/com/android/server/am/ActivityManagerService.java @@ -17,28 +17,59 @@ package com.android.server.am; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static com.android.internal.util.XmlUtils.readIntAttribute; +import static com.android.internal.util.XmlUtils.readLongAttribute; +import static com.android.internal.util.XmlUtils.writeIntAttribute; +import static com.android.internal.util.XmlUtils.writeLongAttribute; +import static org.xmlpull.v1.XmlPullParser.END_DOCUMENT; +import static org.xmlpull.v1.XmlPullParser.START_TAG; + +import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; import android.app.AppOpsManager; import android.appwidget.AppWidgetManager; +import android.util.ArrayMap; import com.android.internal.R; +import com.android.internal.annotations.GuardedBy; +import com.android.internal.app.IAppOpsService; +import com.android.internal.app.ProcessStats; +import com.android.internal.app.ResolverActivity; +import com.android.internal.os.BackgroundThread; import com.android.internal.os.BatteryStatsImpl; -import com.android.internal.os.ProcessStats; +import com.android.internal.os.ProcessCpuTracker; +import com.android.internal.os.TransferPipe; +import com.android.internal.util.FastPrintWriter; +import com.android.internal.util.FastXmlSerializer; +import com.android.internal.util.MemInfoReader; +import com.android.internal.util.Preconditions; import com.android.server.AppOpsService; import com.android.server.AttributeCache; import com.android.server.IntentResolver; -import com.android.server.ProcessMap; +import com.android.internal.app.ProcessMap; import com.android.server.SystemServer; import com.android.server.Watchdog; import com.android.server.am.ActivityStack.ActivityState; import com.android.server.firewall.IntentFirewall; import com.android.server.pm.UserManagerService; import com.android.server.wm.AppTransition; +import com.android.server.wm.StackBox; import com.android.server.wm.WindowManagerService; +import com.google.android.collect.Lists; +import com.google.android.collect.Maps; import dalvik.system.Zygote; +import libcore.io.IoUtils; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + import android.app.Activity; import android.app.ActivityManager; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.ActivityManager.StackBoxInfo; +import android.app.ActivityManager.StackInfo; import android.app.ActivityManagerNative; import android.app.ActivityOptions; import android.app.ActivityThread; @@ -84,6 +115,7 @@ import android.content.pm.IPackageManager; import android.content.pm.InstrumentationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; import android.content.pm.UserInfo; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.PathPermission; @@ -124,7 +156,9 @@ import android.os.SystemProperties; import android.os.UpdateLock; import android.os.UserHandle; import android.provider.Settings; +import android.text.format.DateUtils; import android.text.format.Time; +import android.util.AtomicFile; import android.util.EventLog; import android.util.Log; import android.util.Pair; @@ -132,6 +166,7 @@ import android.util.PrintWriterPrinter; import android.util.Slog; import android.util.SparseArray; import android.util.TimeUtils; +import android.util.Xml; import android.view.Gravity; import android.view.LayoutInflater; import android.view.View; @@ -139,7 +174,6 @@ import android.view.WindowManager; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; -import java.io.BufferedReader; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.File; @@ -166,41 +200,45 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicLong; -public final class ActivityManagerService extends ActivityManagerNative +public final class ActivityManagerService extends ActivityManagerNative implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback { private static final String USER_DATA_DIR = "/data/user/"; static final String TAG = "ActivityManager"; static final String TAG_MU = "ActivityManagerServiceMU"; static final boolean DEBUG = false; static final boolean localLOGV = DEBUG; - static final boolean DEBUG_SWITCH = localLOGV || false; - static final boolean DEBUG_TASKS = localLOGV || false; - static final boolean DEBUG_THUMBNAILS = localLOGV || false; - static final boolean DEBUG_PAUSE = localLOGV || false; - static final boolean DEBUG_OOM_ADJ = localLOGV || false; - static final boolean DEBUG_TRANSITION = localLOGV || false; + static final boolean DEBUG_BACKUP = localLOGV || false; static final boolean DEBUG_BROADCAST = localLOGV || false; - static final boolean DEBUG_BACKGROUND_BROADCAST = DEBUG_BROADCAST || false; static final boolean DEBUG_BROADCAST_LIGHT = DEBUG_BROADCAST || false; - static final boolean DEBUG_SERVICE = localLOGV || false; - static final boolean DEBUG_SERVICE_EXECUTING = localLOGV || false; - static final boolean DEBUG_VISBILITY = localLOGV || false; - static final boolean DEBUG_PROCESSES = localLOGV || false; - static final boolean DEBUG_PROCESS_OBSERVERS = localLOGV || false; + static final boolean DEBUG_BACKGROUND_BROADCAST = DEBUG_BROADCAST || false; static final boolean DEBUG_CLEANUP = localLOGV || false; - static final boolean DEBUG_PROVIDER = localLOGV || false; - static final boolean DEBUG_URI_PERMISSION = localLOGV || false; - static final boolean DEBUG_USER_LEAVING = localLOGV || false; - static final boolean DEBUG_RESULTS = localLOGV || false; - static final boolean DEBUG_BACKUP = localLOGV || false; static final boolean DEBUG_CONFIGURATION = localLOGV || false; + static final boolean DEBUG_FOCUS = false; + static final boolean DEBUG_IMMERSIVE = localLOGV || false; + static final boolean DEBUG_MU = localLOGV || false; + static final boolean DEBUG_OOM_ADJ = localLOGV || false; + static final boolean DEBUG_PAUSE = localLOGV || false; static final boolean DEBUG_POWER = localLOGV || false; static final boolean DEBUG_POWER_QUICK = DEBUG_POWER || false; - static final boolean DEBUG_MU = localLOGV || false; - static final boolean DEBUG_IMMERSIVE = localLOGV || false; + static final boolean DEBUG_PROCESS_OBSERVERS = localLOGV || false; + static final boolean DEBUG_PROCESSES = localLOGV || false; + static final boolean DEBUG_PROVIDER = localLOGV || false; + static final boolean DEBUG_RESULTS = localLOGV || false; + static final boolean DEBUG_SERVICE = localLOGV || false; + static final boolean DEBUG_SERVICE_EXECUTING = localLOGV || false; + static final boolean DEBUG_STACK = localLOGV || false; + static final boolean DEBUG_SWITCH = localLOGV || false; + static final boolean DEBUG_TASKS = localLOGV || false; + static final boolean DEBUG_THUMBNAILS = localLOGV || false; + static final boolean DEBUG_TRANSITION = localLOGV || false; + static final boolean DEBUG_URI_PERMISSION = localLOGV || false; + static final boolean DEBUG_USER_LEAVING = localLOGV || false; + static final boolean DEBUG_VISBILITY = localLOGV || false; + static final boolean DEBUG_PSS = localLOGV || false; + static final boolean DEBUG_LOCKSCREEN = localLOGV || false; static final boolean VALIDATE_TOKENS = false; static final boolean SHOW_ACTIVITY_START_TIME = true; - + // Control over CPU and battery monitoring. static final long BATTERY_STATS_TIME = 30*60*1000; // write battery stats every 30 minutes. static final boolean MONITOR_CPU_USAGE = true; @@ -210,14 +248,14 @@ public final class ActivityManagerService extends ActivityManagerNative // The flags that are set for all calls we make to the package manager. static final int STOCK_PM_FLAGS = PackageManager.GET_SHARED_LIBRARY_FILES; - + private static final String SYSTEM_DEBUGGABLE = "ro.debuggable"; static final boolean IS_USER_BUILD = "user".equals(Build.TYPE); // Maximum number of recent tasks that we can remember. - static final int MAX_RECENT_TASKS = 20; - + static final int MAX_RECENT_TASKS = ActivityManager.isLowRamDeviceStatic() ? 10 : 20; + // Amount of time after a call to stopAppSwitches() during which we will // prevent further untrusted switches from happening. static final long APP_SWITCH_DELAY_TIME = 5*1000; @@ -238,6 +276,13 @@ public final class ActivityManagerService extends ActivityManagerNative // The minimum amount of time between successive GC requests for a process. static final int GC_MIN_INTERVAL = 60*1000; + // The minimum amount of time between successive PSS requests for a process. + static final int FULL_PSS_MIN_INTERVAL = 10*60*1000; + + // The minimum amount of time between successive PSS requests for a process + // when the request is due to the memory state being lowered. + static final int FULL_PSS_LOWERED_INTERVAL = 2*60*1000; + // The rate at which we check for apps using excessive power -- 15 mins. static final int POWER_CHECK_DELAY = (DEBUG_POWER_QUICK ? 2 : 15) * 60*1000; @@ -266,14 +311,19 @@ public final class ActivityManagerService extends ActivityManagerNative // Maximum number of users we allow to be running at a time. static final int MAX_RUNNING_USERS = 3; - // How long to wait in getTopActivityExtras for the activity to respond with the result. - static final int PENDING_ACTIVITY_RESULT_TIMEOUT = 2*2000; + // How long to wait in getAssistContextExtras for the activity and foreground services + // to respond with the result. + static final int PENDING_ASSIST_EXTRAS_TIMEOUT = 500; + + // Maximum number of persisted Uri grants a package is allowed + static final int MAX_PERSISTED_URI_GRANTS = 128; static final int MY_PID = Process.myPid(); - + static final String[] EMPTY_STRING_ARRAY = new String[0]; - public ActivityStack mMainStack; + /** Run all ActivityStacks through this */ + ActivityStackSupervisor mStackSupervisor; public IntentFirewall mIntentFirewall; @@ -289,14 +339,22 @@ public final class ActivityManagerService extends ActivityManagerNative * due to app switches being disabled. */ static class PendingActivityLaunch { - ActivityRecord r; - ActivityRecord sourceRecord; - int startFlags; + final ActivityRecord r; + final ActivityRecord sourceRecord; + final int startFlags; + final ActivityStack stack; + + PendingActivityLaunch(ActivityRecord _r, ActivityRecord _sourceRecord, + int _startFlags, ActivityStack _stack) { + r = _r; + sourceRecord = _sourceRecord; + startFlags = _startFlags; + stack = _stack; + } } - + final ArrayList<PendingActivityLaunch> mPendingActivityLaunches = new ArrayList<PendingActivityLaunch>(); - BroadcastQueue mFgBroadcastQueue; BroadcastQueue mBgBroadcastQueue; @@ -332,18 +390,18 @@ public final class ActivityManagerService extends ActivityManagerNative /** * List of intents that were used to start the most recent tasks. */ - final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>(); + private final ArrayList<TaskRecord> mRecentTasks = new ArrayList<TaskRecord>(); - public class PendingActivityExtras extends Binder implements Runnable { + public class PendingAssistExtras extends Binder implements Runnable { public final ActivityRecord activity; public boolean haveResult = false; public Bundle result = null; - public PendingActivityExtras(ActivityRecord _activity) { + public PendingAssistExtras(ActivityRecord _activity) { activity = _activity; } @Override public void run() { - Slog.w(TAG, "getTopActivityExtras failed: timeout retrieving from " + activity); + Slog.w(TAG, "getAssistContextExtras failed: timeout retrieving from " + activity); synchronized (this) { haveResult = true; notifyAll(); @@ -351,8 +409,8 @@ public final class ActivityManagerService extends ActivityManagerNative } } - final ArrayList<PendingActivityExtras> mPendingActivityExtras - = new ArrayList<PendingActivityExtras>(); + final ArrayList<PendingAssistExtras> mPendingAssistExtras + = new ArrayList<PendingAssistExtras>(); /** * Process management. @@ -368,6 +426,12 @@ public final class ActivityManagerService extends ActivityManagerNative final ProcessMap<ProcessRecord> mProcessNames = new ProcessMap<ProcessRecord>(); /** + * Tracking long-term execution of processes to look for abuse and other + * bad app behavior. + */ + final ProcessStatsService mProcessStats; + + /** * The currently running isolated processes. */ final SparseArray<ProcessRecord> mIsolatedProcesses = new SparseArray<ProcessRecord>(); @@ -382,7 +446,7 @@ public final class ActivityManagerService extends ActivityManagerNative * The currently running heavy-weight process, if any. */ ProcessRecord mHeavyWeightProcess = null; - + /** * The last time that various processes have crashed. */ @@ -416,51 +480,64 @@ public final class ActivityManagerService extends ActivityManagerNative int pid; IBinder token; } - final SparseArray<ForegroundToken> mForegroundProcesses - = new SparseArray<ForegroundToken>(); - + final SparseArray<ForegroundToken> mForegroundProcesses = new SparseArray<ForegroundToken>(); + /** * List of records for processes that someone had tried to start before the * system was ready. We don't start them at that point, but ensure they * are started by the time booting is complete. */ - final ArrayList<ProcessRecord> mProcessesOnHold - = new ArrayList<ProcessRecord>(); + final ArrayList<ProcessRecord> mProcessesOnHold = new ArrayList<ProcessRecord>(); /** * List of persistent applications that are in the process * of being started. */ - final ArrayList<ProcessRecord> mPersistentStartingProcesses - = new ArrayList<ProcessRecord>(); + final ArrayList<ProcessRecord> mPersistentStartingProcesses = new ArrayList<ProcessRecord>(); /** * Processes that are being forcibly torn down. */ - final ArrayList<ProcessRecord> mRemovedProcesses - = new ArrayList<ProcessRecord>(); + final ArrayList<ProcessRecord> mRemovedProcesses = new ArrayList<ProcessRecord>(); /** * List of running applications, sorted by recent usage. * The first entry in the list is the least recently used. - * It contains ApplicationRecord objects. This list does NOT include - * any persistent application records (since we never want to exit them). */ - final ArrayList<ProcessRecord> mLruProcesses - = new ArrayList<ProcessRecord>(); + final ArrayList<ProcessRecord> mLruProcesses = new ArrayList<ProcessRecord>(); + + /** + * Where in mLruProcesses that the processes hosting activities start. + */ + int mLruProcessActivityStart = 0; + + /** + * Where in mLruProcesses that the processes hosting services start. + * This is after (lower index) than mLruProcessesActivityStart. + */ + int mLruProcessServiceStart = 0; /** * List of processes that should gc as soon as things are idle. */ - final ArrayList<ProcessRecord> mProcessesToGc - = new ArrayList<ProcessRecord>(); + final ArrayList<ProcessRecord> mProcessesToGc = new ArrayList<ProcessRecord>(); + + /** + * Processes we want to collect PSS data from. + */ + final ArrayList<ProcessRecord> mPendingPssProcesses = new ArrayList<ProcessRecord>(); + + /** + * Last time we requested PSS data of all processes. + */ + long mLastFullPssTime = SystemClock.uptimeMillis(); /** * This is the process holding what we currently consider to be * the "home" activity. */ ProcessRecord mHomeProcess; - + /** * This is the process holding the activity the user last visited that * is in a different process from the one they are currently in. @@ -505,11 +582,6 @@ public final class ActivityManagerService extends ActivityManagerNative final CompatModePackages mCompatModePackages; /** - * Set of PendingResultRecord objects that are currently active. - */ - final HashSet mPendingResultRecords = new HashSet(); - - /** * Set of IntentSenderRecord objects that are currently active. */ final HashMap<PendingIntentRecord.Key, WeakReference<PendingIntentRecord>> mIntentSenderRecords @@ -538,7 +610,8 @@ public final class ActivityManagerService extends ActivityManagerNative * broadcasts. Hash keys are the receiver IBinder, hash value is * a ReceiverList. */ - final HashMap mRegisteredReceivers = new HashMap(); + final HashMap<IBinder, ReceiverList> mRegisteredReceivers = + new HashMap<IBinder, ReceiverList>(); /** * Resolver for broadcast intents to registered receivers. @@ -585,8 +658,8 @@ public final class ActivityManagerService extends ActivityManagerNative * by the user ID the sticky is for, and can include UserHandle.USER_ALL * for stickies that are sent to all users. */ - final SparseArray<HashMap<String, ArrayList<Intent>>> mStickyBroadcasts = - new SparseArray<HashMap<String, ArrayList<Intent>>>(); + final SparseArray<ArrayMap<String, ArrayList<Intent>>> mStickyBroadcasts = + new SparseArray<ArrayMap<String, ArrayList<Intent>>>(); final ActiveServices mServices; @@ -600,13 +673,8 @@ public final class ActivityManagerService extends ActivityManagerNative * List of PendingThumbnailsRecord objects of clients who are still * waiting to receive all of the thumbnails for a task. */ - final ArrayList mPendingThumbnails = new ArrayList(); - - /** - * List of HistoryRecord objects that have been finished and must - * still report back to a pending thumbnail receiver. - */ - final ArrayList mCancelledThumbnails = new ArrayList(); + final ArrayList<PendingThumbnailsRecord> mPendingThumbnails = + new ArrayList<PendingThumbnailsRecord>(); final ProviderMap mProviderMap; @@ -619,10 +687,28 @@ public final class ActivityManagerService extends ActivityManagerNative = new ArrayList<ContentProviderRecord>(); /** - * Global set of specific Uri permissions that have been granted. + * File storing persisted {@link #mGrantedUriPermissions}. */ - final private SparseArray<HashMap<Uri, UriPermission>> mGrantedUriPermissions - = new SparseArray<HashMap<Uri, UriPermission>>(); + private final AtomicFile mGrantFile; + + /** XML constants used in {@link #mGrantFile} */ + private static final String TAG_URI_GRANTS = "uri-grants"; + private static final String TAG_URI_GRANT = "uri-grant"; + private static final String ATTR_USER_HANDLE = "userHandle"; + private static final String ATTR_SOURCE_PKG = "sourcePkg"; + private static final String ATTR_TARGET_PKG = "targetPkg"; + private static final String ATTR_URI = "uri"; + private static final String ATTR_MODE_FLAGS = "modeFlags"; + private static final String ATTR_CREATED_TIME = "createdTime"; + + /** + * Global set of specific {@link Uri} permissions that have been granted. + * This optimized lookup structure maps from {@link UriPermission#targetUid} + * to {@link UriPermission#uri} to {@link UriPermission}. + */ + @GuardedBy("this") + private final SparseArray<ArrayMap<Uri, UriPermission>> + mGrantedUriPermissions = new SparseArray<ArrayMap<Uri, UriPermission>>(); CoreSettingsObserver mCoreSettingsObserver; @@ -647,12 +733,12 @@ public final class ActivityManagerService extends ActivityManagerNative * any user id that can impact battery performance. */ final BatteryStatsService mBatteryStatsService; - + /** * Information about component usage */ final UsageStatsService mUsageStatsService; - + /** * Information about and control over application operations */ @@ -670,7 +756,7 @@ public final class ActivityManagerService extends ActivityManagerNative * configurations. */ int mConfigurationSeq = 0; - + /** * Hardware-reported OpenGLES version. */ @@ -686,7 +772,7 @@ public final class ActivityManagerService extends ActivityManagerNative * Temporary to avoid allocations. Protected by main lock. */ final StringBuilder mStringBuilder = new StringBuilder(256); - + /** * Used to control how we initialize the service. */ @@ -707,7 +793,7 @@ public final class ActivityManagerService extends ActivityManagerNative int mFactoryTest; boolean mCheckedForSetup; - + /** * The time at which we will allow normal application switches again, * after a call to {@link #stopAppSwitches()}. @@ -719,7 +805,7 @@ public final class ActivityManagerService extends ActivityManagerNative * is set; any switches after that will clear the time. */ boolean mDidAppSwitch; - + /** * Last time (in realtime) at which we checked for power usage. */ @@ -752,14 +838,6 @@ public final class ActivityManagerService extends ActivityManagerNative boolean mShuttingDown = false; /** - * Task identifier that activities are currently being started - * in. Incremented each time a new task is created. - * todo: Replace this with a TokenSpace class that generates non-repeating - * integers that won't wrap. - */ - int mCurTask = 1; - - /** * Current sequence id for oom_adj computation traversal. */ int mAdjSeq = 0; @@ -770,36 +848,66 @@ public final class ActivityManagerService extends ActivityManagerNative int mLruSeq = 0; /** - * Keep track of the non-hidden/empty process we last found, to help - * determine how to distribute hidden/empty processes next time. + * Keep track of the non-cached/empty process we last found, to help + * determine how to distribute cached/empty processes next time. */ - int mNumNonHiddenProcs = 0; + int mNumNonCachedProcs = 0; /** - * Keep track of the number of hidden procs, to balance oom adj + * Keep track of the number of cached hidden procs, to balance oom adj * distribution between those and empty procs. */ - int mNumHiddenProcs = 0; + int mNumCachedHiddenProcs = 0; /** * Keep track of the number of service processes we last found, to * determine on the next iteration which should be B services. */ int mNumServiceProcs = 0; + int mNewNumAServiceProcs = 0; int mNewNumServiceProcs = 0; /** - * System monitoring: number of processes that died since the last - * N procs were started. + * Allow the current computed overall memory level of the system to go down? + * This is set to false when we are killing processes for reasons other than + * memory management, so that the now smaller process list will not be taken as + * an indication that memory is tighter. */ - int[] mProcDeaths = new int[20]; - + boolean mAllowLowerMemLevel = false; + + /** + * The last computed memory level, for holding when we are in a state that + * processes are going away for other reasons. + */ + int mLastMemoryLevel = ProcessStats.ADJ_MEM_FACTOR_NORMAL; + + /** + * The last total number of process we have, to determine if changes actually look + * like a shrinking number of process due to lower RAM. + */ + int mLastNumProcesses; + + /** + * The uptime of the last time we performed idle maintenance. + */ + long mLastIdleTime = SystemClock.uptimeMillis(); + + /** + * Total time spent with RAM that has been added in the past since the last idle time. + */ + long mLowRamTimeSinceLastIdle = 0; + + /** + * If RAM is currently low, when that horrible situatin started. + */ + long mLowRamStartTime = 0; + /** * This is set if we had to do a delayed dexopt of an app before launching * it, to increasing the ANR timeouts in that case. */ boolean mDidDexOpt; - + String mDebugApp = null; boolean mWaitForDebugger = false; boolean mDebugTransient = false; @@ -835,19 +943,19 @@ public final class ActivityManagerService extends ActivityManagerNative = new ArrayList<ProcessChangeItem>(); /** - * Runtime statistics collection thread. This object's lock is used to + * Runtime CPU use collection thread. This object's lock is used to * protect all related state. */ - final Thread mProcessStatsThread; - + final Thread mProcessCpuThread; + /** * Used to collect process stats when showing not responding dialog. - * Protected by mProcessStatsThread. + * Protected by mProcessCpuThread. */ - final ProcessStats mProcessStats = new ProcessStats( + final ProcessCpuTracker mProcessCpuTracker = new ProcessCpuTracker( MONITOR_THREAD_CPU_USAGE); final AtomicLong mLastCpuTime = new AtomicLong(0); - final AtomicBoolean mProcessStatsMutexFree = new AtomicBoolean(true); + final AtomicBoolean mProcessCpuMutexFree = new AtomicBoolean(true); long mLastWriteTime = 0; @@ -862,7 +970,7 @@ public final class ActivityManagerService extends ActivityManagerNative */ boolean mBooted = false; - int mProcessLimit = ProcessList.MAX_HIDDEN_APPS; + int mProcessLimit = ProcessList.MAX_CACHED_APPS; int mProcessLimitOverride = -1; WindowManagerService mWindowManager; @@ -870,8 +978,7 @@ public final class ActivityManagerService extends ActivityManagerNative static ActivityManagerService mSelf; static ActivityThread mSystemThread; - private int mCurrentUserId = 0; - private int[] mCurrentUserArray = new int[] { 0 }; + int mCurrentUserId = 0; private UserManagerService mUserManager; private final class AppDeathRecipient implements IBinder.DeathRecipient { @@ -889,6 +996,7 @@ public final class ActivityManagerService extends ActivityManagerNative mAppThread = thread; } + @Override public void binderDied() { if (localLOGV) Slog.v( TAG, "Death received in " + this @@ -917,20 +1025,23 @@ public final class ActivityManagerService extends ActivityManagerNative static final int CANCEL_HEAVY_NOTIFICATION_MSG = 25; static final int SHOW_STRICT_MODE_VIOLATION_MSG = 26; static final int CHECK_EXCESSIVE_WAKE_LOCKS_MSG = 27; - static final int CLEAR_DNS_CACHE = 28; - static final int UPDATE_HTTP_PROXY = 29; + static final int CLEAR_DNS_CACHE_MSG = 28; + static final int UPDATE_HTTP_PROXY_MSG = 29; static final int SHOW_COMPAT_MODE_DIALOG_MSG = 30; static final int DISPATCH_PROCESSES_CHANGED = 31; static final int DISPATCH_PROCESS_DIED = 32; - static final int REPORT_MEM_USAGE = 33; + static final int REPORT_MEM_USAGE_MSG = 33; static final int REPORT_USER_SWITCH_MSG = 34; static final int CONTINUE_USER_SWITCH_MSG = 35; static final int USER_SWITCH_TIMEOUT_MSG = 36; static final int IMMERSIVE_MODE_LOCK_MSG = 37; + static final int PERSIST_URI_GRANTS_MSG = 38; + static final int REQUEST_ALL_PSS_MSG = 39; static final int FIRST_ACTIVITY_STACK_MSG = 100; static final int FIRST_BROADCAST_QUEUE_MSG = 200; static final int FIRST_COMPAT_MODE_MSG = 300; + static final int FIRST_SUPERVISOR_STACK_MSG = 100; AlertDialog mUidAlert; CompatModeDialog mCompatModeDialog; @@ -947,10 +1058,11 @@ public final class ActivityManagerService extends ActivityManagerNative // if (localLOGV) Slog.v(TAG, "Handler started!"); //} + @Override public void handleMessage(Message msg) { switch (msg.what) { case SHOW_ERROR_MSG: { - HashMap data = (HashMap) msg.obj; + HashMap<String, Object> data = (HashMap<String, Object>) msg.obj; boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; synchronized (ActivityManagerService.this) { @@ -985,18 +1097,18 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + ensureBootCompleted(); } break; case SHOW_NOT_RESPONDING_MSG: { synchronized (ActivityManagerService.this) { - HashMap data = (HashMap) msg.obj; + HashMap<String, Object> data = (HashMap<String, Object>) msg.obj; ProcessRecord proc = (ProcessRecord)data.get("app"); if (proc != null && proc.anrDialog != null) { Slog.e(TAG, "App already has anr dialog: " + proc); return; } - + Intent intent = new Intent("android.intent.action.ANR"); if (!mProcessesReady) { intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY @@ -1017,7 +1129,7 @@ public final class ActivityManagerService extends ActivityManagerNative killAppAtUsersRequest(proc, null); } } - + ensureBootCompleted(); } break; case SHOW_STRICT_MODE_VIOLATION_MSG: { @@ -1105,7 +1217,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } } break; - case CLEAR_DNS_CACHE: { + case CLEAR_DNS_CACHE_MSG: { synchronized (ActivityManagerService.this) { for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { ProcessRecord r = mLruProcesses.get(i); @@ -1119,22 +1231,24 @@ public final class ActivityManagerService extends ActivityManagerNative } } } break; - case UPDATE_HTTP_PROXY: { + case UPDATE_HTTP_PROXY_MSG: { ProxyProperties proxy = (ProxyProperties)msg.obj; String host = ""; String port = ""; String exclList = ""; + String pacFileUrl = null; if (proxy != null) { host = proxy.getHost(); port = Integer.toString(proxy.getPort()); exclList = proxy.getExclusionList(); + pacFileUrl = proxy.getPacFileUrl(); } synchronized (ActivityManagerService.this) { for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { ProcessRecord r = mLruProcesses.get(i); if (r.thread != null) { try { - r.thread.setHttpProxy(host, port, exclList); + r.thread.setHttpProxy(host, port, exclList, pacFileUrl); } catch (RemoteException ex) { Slog.w(TAG, "Failed to update http proxy for: " + r.info.processName); @@ -1189,9 +1303,11 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (ActivityManagerService.this) { int appid = msg.arg1; boolean restart = (msg.arg2 == 1); - String pkg = (String) msg.obj; + Bundle bundle = (Bundle)msg.obj; + String pkg = bundle.getString("pkg"); + String reason = bundle.getString("reason"); forceStopPackageLocked(pkg, appid, restart, false, true, false, - UserHandle.USER_ALL); + UserHandle.USER_ALL, reason); } } break; case FINALIZE_PENDING_INTENT_MSG: { @@ -1202,13 +1318,13 @@ public final class ActivityManagerService extends ActivityManagerNative if (inm == null) { return; } - + ActivityRecord root = (ActivityRecord)msg.obj; ProcessRecord process = root.app; if (process == null) { return; } - + try { Context context = mContext.createPackageContext(process.info.packageName, 0); String text = mContext.getString(R.string.heavy_weight_notification, @@ -1226,7 +1342,7 @@ public final class ActivityManagerService extends ActivityManagerNative PendingIntent.getActivityAsUser(mContext, 0, root.intent, PendingIntent.FLAG_CANCEL_CURRENT, null, new UserHandle(root.userId))); - + try { int[] outId = new int[1]; inm.enqueueNotificationWithTag("android", "android", null, @@ -1301,64 +1417,176 @@ public final class ActivityManagerService extends ActivityManagerNative dispatchProcessDied(pid, uid); break; } - case REPORT_MEM_USAGE: { - boolean isDebuggable = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); - if (!isDebuggable) { - return; - } - synchronized (ActivityManagerService.this) { - long now = SystemClock.uptimeMillis(); - if (now < (mLastMemUsageReportTime+5*60*1000)) { - // Don't report more than every 5 minutes to somewhat - // avoid spamming. - return; - } - mLastMemUsageReportTime = now; - } + case REPORT_MEM_USAGE_MSG: { + final ArrayList<ProcessMemInfo> memInfos = (ArrayList<ProcessMemInfo>)msg.obj; Thread thread = new Thread() { @Override public void run() { - StringBuilder dropBuilder = new StringBuilder(1024); + final SparseArray<ProcessMemInfo> infoMap + = new SparseArray<ProcessMemInfo>(memInfos.size()); + for (int i=0, N=memInfos.size(); i<N; i++) { + ProcessMemInfo mi = memInfos.get(i); + infoMap.put(mi.pid, mi); + } + updateCpuStatsNow(); + synchronized (mProcessCpuThread) { + final int N = mProcessCpuTracker.countStats(); + for (int i=0; i<N; i++) { + ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); + if (st.vsize > 0) { + long pss = Debug.getPss(st.pid, null); + if (pss > 0) { + if (infoMap.indexOfKey(st.pid) < 0) { + ProcessMemInfo mi = new ProcessMemInfo(st.name, st.pid, + ProcessList.NATIVE_ADJ, -1, "native", null); + mi.pss = pss; + memInfos.add(mi); + } + } + } + } + } + + long totalPss = 0; + for (int i=0, N=memInfos.size(); i<N; i++) { + ProcessMemInfo mi = memInfos.get(i); + if (mi.pss == 0) { + mi.pss = Debug.getPss(mi.pid, null); + } + totalPss += mi.pss; + } + Collections.sort(memInfos, new Comparator<ProcessMemInfo>() { + @Override public int compare(ProcessMemInfo lhs, ProcessMemInfo rhs) { + if (lhs.oomAdj != rhs.oomAdj) { + return lhs.oomAdj < rhs.oomAdj ? -1 : 1; + } + if (lhs.pss != rhs.pss) { + return lhs.pss < rhs.pss ? 1 : -1; + } + return 0; + } + }); + + StringBuilder tag = new StringBuilder(128); + StringBuilder stack = new StringBuilder(128); + tag.append("Low on memory -- "); + appendMemBucket(tag, totalPss, "total", false); + appendMemBucket(stack, totalPss, "total", true); + StringBuilder logBuilder = new StringBuilder(1024); + logBuilder.append("Low on memory:\n"); + + boolean firstLine = true; + int lastOomAdj = Integer.MIN_VALUE; + for (int i=0, N=memInfos.size(); i<N; i++) { + ProcessMemInfo mi = memInfos.get(i); + + if (mi.oomAdj != ProcessList.NATIVE_ADJ + && (mi.oomAdj < ProcessList.SERVICE_ADJ + || mi.oomAdj == ProcessList.HOME_APP_ADJ + || mi.oomAdj == ProcessList.PREVIOUS_APP_ADJ)) { + if (lastOomAdj != mi.oomAdj) { + lastOomAdj = mi.oomAdj; + if (mi.oomAdj <= ProcessList.FOREGROUND_APP_ADJ) { + tag.append(" / "); + } + if (mi.oomAdj >= ProcessList.FOREGROUND_APP_ADJ) { + if (firstLine) { + stack.append(":"); + firstLine = false; + } + stack.append("\n\t at "); + } else { + stack.append("$"); + } + } else { + tag.append(" "); + stack.append("$"); + } + if (mi.oomAdj <= ProcessList.FOREGROUND_APP_ADJ) { + appendMemBucket(tag, mi.pss, mi.name, false); + } + appendMemBucket(stack, mi.pss, mi.name, true); + if (mi.oomAdj >= ProcessList.FOREGROUND_APP_ADJ + && ((i+1) >= N || memInfos.get(i+1).oomAdj != lastOomAdj)) { + stack.append("("); + for (int k=0; k<DUMP_MEM_OOM_ADJ.length; k++) { + if (DUMP_MEM_OOM_ADJ[k] == mi.oomAdj) { + stack.append(DUMP_MEM_OOM_LABEL[k]); + stack.append(":"); + stack.append(DUMP_MEM_OOM_ADJ[k]); + } + } + stack.append(")"); + } + } + + logBuilder.append(" "); + logBuilder.append(ProcessList.makeOomAdjString(mi.oomAdj)); + logBuilder.append(' '); + logBuilder.append(ProcessList.makeProcStateString(mi.procState)); + logBuilder.append(' '); + ProcessList.appendRamKb(logBuilder, mi.pss); + logBuilder.append(" kB: "); + logBuilder.append(mi.name); + logBuilder.append(" ("); + logBuilder.append(mi.pid); + logBuilder.append(") "); + logBuilder.append(mi.adjType); + logBuilder.append('\n'); + if (mi.adjReason != null) { + logBuilder.append(" "); + logBuilder.append(mi.adjReason); + logBuilder.append('\n'); + } + } + + logBuilder.append(" "); + ProcessList.appendRamKb(logBuilder, totalPss); + logBuilder.append(" kB: TOTAL\n"); + + long[] infos = new long[Debug.MEMINFO_COUNT]; + Debug.getMemInfo(infos); + logBuilder.append(" MemInfo: "); + logBuilder.append(infos[Debug.MEMINFO_SLAB]).append(" kB slab, "); + logBuilder.append(infos[Debug.MEMINFO_SHMEM]).append(" kB shmem, "); + logBuilder.append(infos[Debug.MEMINFO_BUFFERS]).append(" kB buffers, "); + logBuilder.append(infos[Debug.MEMINFO_CACHED]).append(" kB cached, "); + logBuilder.append(infos[Debug.MEMINFO_FREE]).append(" kB free\n"); + if (infos[Debug.MEMINFO_ZRAM_TOTAL] != 0) { + logBuilder.append(" ZRAM: "); + logBuilder.append(infos[Debug.MEMINFO_ZRAM_TOTAL]); + logBuilder.append(" kB RAM, "); + logBuilder.append(infos[Debug.MEMINFO_SWAP_TOTAL]); + logBuilder.append(" kB swap total, "); + logBuilder.append(infos[Debug.MEMINFO_SWAP_FREE]); + logBuilder.append(" kB swap free\n"); + } + Slog.i(TAG, logBuilder.toString()); + + StringBuilder dropBuilder = new StringBuilder(1024); + /* StringWriter oomSw = new StringWriter(); - PrintWriter oomPw = new PrintWriter(oomSw); + PrintWriter oomPw = new FastPrintWriter(oomSw, false, 256); StringWriter catSw = new StringWriter(); - PrintWriter catPw = new PrintWriter(catSw); + PrintWriter catPw = new FastPrintWriter(catSw, false, 256); String[] emptyArgs = new String[] { }; - StringBuilder tag = new StringBuilder(128); - StringBuilder stack = new StringBuilder(128); - tag.append("Low on memory -- "); - dumpApplicationMemoryUsage(null, oomPw, " ", emptyArgs, true, catPw, - tag, stack); + dumpApplicationMemoryUsage(null, oomPw, " ", emptyArgs, true, catPw); + oomPw.flush(); + String oomString = oomSw.toString(); + */ dropBuilder.append(stack); dropBuilder.append('\n'); dropBuilder.append('\n'); - String oomString = oomSw.toString(); + dropBuilder.append(logBuilder); + dropBuilder.append('\n'); + /* dropBuilder.append(oomString); dropBuilder.append('\n'); - logBuilder.append(oomString); - try { - java.lang.Process proc = Runtime.getRuntime().exec(new String[] { - "procrank", }); - final InputStreamReader converter = new InputStreamReader( - proc.getInputStream()); - BufferedReader in = new BufferedReader(converter); - String line; - while (true) { - line = in.readLine(); - if (line == null) { - break; - } - if (line.length() > 0) { - logBuilder.append(line); - logBuilder.append('\n'); - } - dropBuilder.append(line); - dropBuilder.append('\n'); - } - converter.close(); - } catch (IOException e) { - } + */ + StringWriter catSw = new StringWriter(); synchronized (ActivityManagerService.this) { + PrintWriter catPw = new FastPrintWriter(catSw, false, 256); + String[] emptyArgs = new String[] { }; catPw.println(); dumpProcessesLocked(null, catPw, emptyArgs, 0, false, null); catPw.println(); @@ -1366,11 +1594,13 @@ public final class ActivityManagerService extends ActivityManagerNative false, false, null); catPw.println(); dumpActivitiesLocked(null, catPw, emptyArgs, 0, false, false, null); + catPw.flush(); } dropBuilder.append(catSw.toString()); addErrorToDropBox("lowmem", null, "system_server", null, null, tag.toString(), dropBuilder.toString(), null, null); - Slog.i(TAG, logBuilder.toString()); + //Slog.i(TAG, "Sent to dropbox:"); + //Slog.i(TAG, dropBuilder.toString()); synchronized (ActivityManagerService.this) { long now = SystemClock.uptimeMillis(); if (mLastMemUsageReportTime < now) { @@ -1409,6 +1639,72 @@ public final class ActivityManagerService extends ActivityManagerNative } break; } + case PERSIST_URI_GRANTS_MSG: { + writeGrantedUriPermissions(); + break; + } + case REQUEST_ALL_PSS_MSG: { + requestPssAllProcsLocked(SystemClock.uptimeMillis(), true, false); + break; + } + } + } + }; + + static final int COLLECT_PSS_BG_MSG = 1; + + final Handler mBgHandler = new Handler(BackgroundThread.getHandler().getLooper()) { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case COLLECT_PSS_BG_MSG: { + int i=0, num=0; + long start = SystemClock.uptimeMillis(); + long[] tmp = new long[1]; + do { + ProcessRecord proc; + int procState; + int pid; + synchronized (ActivityManagerService.this) { + if (i >= mPendingPssProcesses.size()) { + if (DEBUG_PSS) Slog.d(TAG, "Collected PSS of " + num + " of " + i + + " processes in " + (SystemClock.uptimeMillis()-start) + "ms"); + mPendingPssProcesses.clear(); + return; + } + proc = mPendingPssProcesses.get(i); + procState = proc.pssProcState; + if (proc.thread != null && procState == proc.setProcState) { + pid = proc.pid; + } else { + proc = null; + pid = 0; + } + i++; + } + if (proc != null) { + long pss = Debug.getPss(pid, tmp); + synchronized (ActivityManagerService.this) { + if (proc.thread != null && proc.setProcState == procState + && proc.pid == pid) { + num++; + proc.lastPssTime = SystemClock.uptimeMillis(); + proc.baseProcessTracker.addPss(pss, tmp[0], true, proc.pkgList); + if (DEBUG_PSS) Slog.d(TAG, "PSS of " + proc.toShortString() + + ": " + pss + " lastPss=" + proc.lastPss + + " state=" + ProcessList.makeProcStateString(procState)); + if (proc.initialIdlePss == 0) { + proc.initialIdlePss = pss; + } + proc.lastPss = pss; + if (procState >= ActivityManager.PROCESS_STATE_HOME) { + proc.lastCachedPss = pss; + } + } + } + } + } while (true); + } } } }; @@ -1417,7 +1713,8 @@ public final class ActivityManagerService extends ActivityManagerNative try { ActivityManagerService m = mSelf; - ServiceManager.addService("activity", m, true); + ServiceManager.addService(Context.ACTIVITY_SERVICE, m, true); + ServiceManager.addService(ProcessStats.SERVICE_NAME, m.mProcessStats); ServiceManager.addService("meminfo", new MemBinder(m)); ServiceManager.addService("gfxinfo", new GraphicsBinder(m)); ServiceManager.addService("dbinfo", new DbBinder(m)); @@ -1430,19 +1727,19 @@ public final class ActivityManagerService extends ActivityManagerNative mSelf.mContext.getPackageManager().getApplicationInfo( "android", STOCK_PM_FLAGS); mSystemThread.installSystemApplicationInfo(info); - + synchronized (mSelf) { - ProcessRecord app = mSelf.newProcessRecordLocked( - mSystemThread.getApplicationThread(), info, + ProcessRecord app = mSelf.newProcessRecordLocked(info, info.processName, false); app.persistent = true; app.pid = MY_PID; app.maxAdj = ProcessList.SYSTEM_ADJ; + app.makeActive(mSystemThread.getApplicationThread(), mSelf.mProcessStats); mSelf.mProcessNames.put(app.processName, app.uid, app); synchronized (mSelf.mPidsSelfLocked) { mSelf.mPidsSelfLocked.put(app.pid, app); } - mSelf.updateLruProcessLocked(app, true); + mSelf.updateLruProcessLocked(app, true, false); } } catch (PackageManager.NameNotFoundException e) { throw new RuntimeException( @@ -1452,6 +1749,8 @@ public final class ActivityManagerService extends ActivityManagerNative public void setWindowManager(WindowManagerService wm) { mWindowManager = wm; + mStackSupervisor.setWindowManager(wm); + wm.createStack(HOME_STACK_ID, -1, StackBox.TASK_STACK_GOES_OVER, 1.0f); } public void startObservingNativeCrashes() { @@ -1480,9 +1779,10 @@ public final class ActivityManagerService extends ActivityManagerNative context.setTheme(android.R.style.Theme_Holo); m.mContext = context; m.mFactoryTest = factoryTest; - m.mMainStack = new ActivityStack(m, context, true, thr.mLooper); m.mIntentFirewall = new IntentFirewall(m.new IntentFirewallInterface()); + m.mStackSupervisor = new ActivityStackSupervisor(m, context, thr.mLooper); + m.mBatteryStatsService.publish(context); m.mUsageStatsService.publish(context); m.mAppOpsService.publish(context); @@ -1493,14 +1793,18 @@ public final class ActivityManagerService extends ActivityManagerNative } m.startRunning(null, null, null, null); - + return context; } public static ActivityManagerService self() { return mSelf; } - + + public IAppOpsService getAppOpsService() { + return mAppOpsService; + } + static class AThread extends Thread { ActivityManagerService mService; Looper mLooper; @@ -1510,6 +1814,7 @@ public final class ActivityManagerService extends ActivityManagerNative super("ActivityManager"); } + @Override public void run() { Looper.prepare(); @@ -1522,6 +1827,7 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (this) { mService = m; mLooper = Looper.myLooper(); + Watchdog.getInstance().addThread(new Handler(mLooper), getName()); notifyAll(); } @@ -1559,8 +1865,7 @@ public final class ActivityManagerService extends ActivityManagerNative return; } - mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, " ", args, - false, null, null, null); + mActivityManagerService.dumpApplicationMemoryUsage(fd, pw, " ", args, false, null); } } @@ -1620,9 +1925,9 @@ public final class ActivityManagerService extends ActivityManagerNative return; } - synchronized (mActivityManagerService.mProcessStatsThread) { - pw.print(mActivityManagerService.mProcessStats.printCurrentLoad()); - pw.print(mActivityManagerService.mProcessStats.printCurrentState( + synchronized (mActivityManagerService.mProcessCpuThread) { + pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentLoad()); + pw.print(mActivityManagerService.mProcessCpuTracker.printCurrentState( SystemClock.uptimeMillis())); } } @@ -1630,9 +1935,9 @@ public final class ActivityManagerService extends ActivityManagerNative private ActivityManagerService() { Slog.i(TAG, "Memory class: " + ActivityManager.staticGetMemoryClass()); - - mFgBroadcastQueue = new BroadcastQueue(this, "foreground", BROADCAST_FG_TIMEOUT); - mBgBroadcastQueue = new BroadcastQueue(this, "background", BROADCAST_BG_TIMEOUT); + + mFgBroadcastQueue = new BroadcastQueue(this, "foreground", BROADCAST_FG_TIMEOUT, false); + mBgBroadcastQueue = new BroadcastQueue(this, "background", BROADCAST_BG_TIMEOUT, true); mBroadcastQueues[0] = mFgBroadcastQueue; mBroadcastQueues[1] = mBgBroadcastQueue; @@ -1650,9 +1955,13 @@ public final class ActivityManagerService extends ActivityManagerNative : mBatteryStatsService.getActiveStatistics().getIsOnBattery(); mBatteryStatsService.getActiveStatistics().setCallback(this); - mUsageStatsService = new UsageStatsService(new File( - systemDir, "usagestats").toString()); + mProcessStats = new ProcessStatsService(this, new File(systemDir, "procstats")); + + mUsageStatsService = new UsageStatsService(new File(systemDir, "usagestats").toString()); mAppOpsService = new AppOpsService(new File(systemDir, "appops.xml")); + + mGrantFile = new AtomicFile(new File(systemDir, "urigrants.xml")); + mHeadless = "1".equals(SystemProperties.get("ro.config.headless", "0")); // User 0 is the first and only user that runs at boot. @@ -1667,14 +1976,15 @@ public final class ActivityManagerService extends ActivityManagerNative mConfiguration.setLocale(Locale.getDefault()); mConfigurationSeq = mConfiguration.seq = 1; - mProcessStats.init(); - + mProcessCpuTracker.init(); + mCompatModePackages = new CompatModePackages(this, systemDir); // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); - mProcessStatsThread = new Thread("ProcessStats") { + mProcessCpuThread = new Thread("CpuTracker") { + @Override public void run() { while (true) { try { @@ -1689,7 +1999,7 @@ public final class ActivityManagerService extends ActivityManagerNative nextCpuDelay = nextWriteDelay; } if (nextCpuDelay > 0) { - mProcessStatsMutexFree.set(true); + mProcessCpuMutexFree.set(true); this.wait(nextCpuDelay); } } @@ -1702,7 +2012,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } }; - mProcessStatsThread.start(); + mProcessCpuThread.start(); } @Override @@ -1712,7 +2022,9 @@ public final class ActivityManagerService extends ActivityManagerNative // We need to tell all apps about the system property change. ArrayList<IBinder> procs = new ArrayList<IBinder>(); synchronized(this) { - for (SparseArray<ProcessRecord> apps : mProcessNames.getMap().values()) { + final int NP = mProcessNames.getMap().size(); + for (int ip=0; ip<NP; ip++) { + SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip); final int NA = apps.size(); for (int ia=0; ia<NA; ia++) { ProcessRecord app = apps.valueAt(ia); @@ -1739,7 +2051,7 @@ public final class ActivityManagerService extends ActivityManagerNative // The activity manager only throws security exceptions, so let's // log all others. if (!(e instanceof SecurityException)) { - Slog.e(TAG, "Activity Manager Crash", e); + Slog.wtf(TAG, "Activity Manager Crash", e); } throw e; } @@ -1750,16 +2062,16 @@ public final class ActivityManagerService extends ActivityManagerNative if (mLastCpuTime.get() >= now - MONITOR_CPU_MIN_TIME) { return; } - if (mProcessStatsMutexFree.compareAndSet(true, false)) { - synchronized (mProcessStatsThread) { - mProcessStatsThread.notify(); + if (mProcessCpuMutexFree.compareAndSet(true, false)) { + synchronized (mProcessCpuThread) { + mProcessCpuThread.notify(); } } } void updateCpuStatsNow() { - synchronized (mProcessStatsThread) { - mProcessStatsMutexFree.set(false); + synchronized (mProcessCpuThread) { + mProcessCpuMutexFree.set(false); final long now = SystemClock.uptimeMillis(); boolean haveNewCpuStats = false; @@ -1767,19 +2079,19 @@ public final class ActivityManagerService extends ActivityManagerNative mLastCpuTime.get() < (now-MONITOR_CPU_MIN_TIME)) { mLastCpuTime.set(now); haveNewCpuStats = true; - mProcessStats.update(); - //Slog.i(TAG, mProcessStats.printCurrentState()); + mProcessCpuTracker.update(); + //Slog.i(TAG, mProcessCpu.printCurrentState()); //Slog.i(TAG, "Total CPU usage: " - // + mProcessStats.getTotalCpuPercent() + "%"); + // + mProcessCpu.getTotalCpuPercent() + "%"); // Slog the cpu usage if the property is set. if ("true".equals(SystemProperties.get("events.cpu"))) { - int user = mProcessStats.getLastUserTime(); - int system = mProcessStats.getLastSystemTime(); - int iowait = mProcessStats.getLastIoWaitTime(); - int irq = mProcessStats.getLastIrqTime(); - int softIrq = mProcessStats.getLastSoftIrqTime(); - int idle = mProcessStats.getLastIdleTime(); + int user = mProcessCpuTracker.getLastUserTime(); + int system = mProcessCpuTracker.getLastSystemTime(); + int iowait = mProcessCpuTracker.getLastIoWaitTime(); + int irq = mProcessCpuTracker.getLastIrqTime(); + int softIrq = mProcessCpuTracker.getLastSoftIrqTime(); + int idle = mProcessCpuTracker.getLastIdleTime(); int total = user + system + iowait + irq + softIrq + idle; if (total == 0) total = 1; @@ -1793,8 +2105,8 @@ public final class ActivityManagerService extends ActivityManagerNative (softIrq * 100) / total); } } - - long[] cpuSpeedTimes = mProcessStats.getLastCpuSpeedTimes(); + + long[] cpuSpeedTimes = mProcessCpuTracker.getLastCpuSpeedTimes(); final BatteryStatsImpl bstats = mBatteryStatsService.getActiveStatistics(); synchronized(bstats) { synchronized(mPidsSelfLocked) { @@ -1803,9 +2115,9 @@ public final class ActivityManagerService extends ActivityManagerNative int perc = bstats.startAddingCpuLocked(); int totalUTime = 0; int totalSTime = 0; - final int N = mProcessStats.countStats(); + final int N = mProcessCpuTracker.countStats(); for (int i=0; i<N; i++) { - ProcessStats.Stats st = mProcessStats.getStats(i); + ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); if (!st.working) { continue; } @@ -1820,6 +2132,15 @@ public final class ActivityManagerService extends ActivityManagerNative st.rel_stime-otherSTime); ps.addSpeedStepTimes(cpuSpeedTimes); pr.curCpuTime += (st.rel_utime+st.rel_stime) * 10; + } else if (st.uid >= Process.FIRST_APPLICATION_UID) { + BatteryStatsImpl.Uid.Proc ps = st.batteryStats; + if (ps == null) { + st.batteryStats = ps = bstats.getProcessStatsLocked(st.uid, + "(Unknown)"); + } + ps.addCpuTimeLocked(st.rel_utime-otherUTime, + st.rel_stime-otherSTime); + ps.addSpeedStepTimes(cpuSpeedTimes); } else { BatteryStatsImpl.Uid.Proc ps = bstats.getProcessStatsLocked(st.name, st.pid); @@ -1843,7 +2164,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + @Override public void batteryNeedsCpuUpdate() { updateCpuStatsNow(); @@ -1881,7 +2202,9 @@ public final class ActivityManagerService extends ActivityManagerNative final void setFocusedActivityLocked(ActivityRecord r) { if (mFocusedActivity != r) { + if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedActivityLocked: r=" + r); mFocusedActivity = r; + mStackSupervisor.setFocusedStack(r); if (r != null) { mWindowManager.setFocusedApp(r.appToken, true); } @@ -1889,6 +2212,31 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override + public void setFocusedStack(int stackId) { + if (DEBUG_FOCUS) Slog.d(TAG, "setFocusedStack: stackId=" + stackId); + synchronized (ActivityManagerService.this) { + ActivityStack stack = mStackSupervisor.getStack(stackId); + if (stack != null) { + ActivityRecord r = stack.topRunningActivityLocked(null); + if (r != null) { + setFocusedActivityLocked(r); + } + } + } + } + + @Override + public void notifyActivityDrawn(IBinder token) { + if (DEBUG_VISBILITY) Slog.d(TAG, "notifyActivityDrawn: token=" + token); + synchronized (this) { + ActivityRecord r= mStackSupervisor.isInAnyStackLocked(token); + if (r != null) { + r.task.stack.notifyActivityDrawnLocked(r); + } + } + } + final void applyUpdateLockStateLocked(ActivityRecord r) { // Modifications to the UpdateLock state are done on our handler, outside // the activity manager's locks. The new state is determined based on the @@ -1899,77 +2247,124 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler.obtainMessage(IMMERSIVE_MODE_LOCK_MSG, (nextState) ? 1 : 0, 0, r)); } - private final void updateLruProcessInternalLocked(ProcessRecord app, int bestPos) { - // put it on the LRU to keep track of when it should be exited. - int lrui = mLruProcesses.indexOf(app); - if (lrui >= 0) mLruProcesses.remove(lrui); - - int i = mLruProcesses.size()-1; - int skipTop = 0; - - app.lruSeq = mLruSeq; - - // compute the new weight for this process. - app.lastActivityTime = SystemClock.uptimeMillis(); + final void showAskCompatModeDialogLocked(ActivityRecord r) { + Message msg = Message.obtain(); + msg.what = SHOW_COMPAT_MODE_DIALOG_MSG; + msg.obj = r.task.askedCompatMode ? null : r; + mHandler.sendMessage(msg); + } + + private final int updateLruProcessInternalLocked(ProcessRecord app, long now, int index, + String what, Object obj, ProcessRecord srcApp) { + app.lastActivityTime = now; + if (app.activities.size() > 0) { - // If this process has activities, we more strongly want to keep - // it around. - app.lruWeight = app.lastActivityTime; - } else if (app.pubProviders.size() > 0) { - // If this process contains content providers, we want to keep - // it a little more strongly. - app.lruWeight = app.lastActivityTime - ProcessList.CONTENT_APP_IDLE_OFFSET; - // Also don't let it kick out the first few "real" hidden processes. - skipTop = ProcessList.MIN_HIDDEN_APPS; - } else { - // If this process doesn't have activities, we less strongly - // want to keep it around, and generally want to avoid getting - // in front of any very recently used activities. - app.lruWeight = app.lastActivityTime - ProcessList.EMPTY_APP_IDLE_OFFSET; - // Also don't let it kick out the first few "real" hidden processes. - skipTop = ProcessList.MIN_HIDDEN_APPS; - } - - while (i >= 0) { - ProcessRecord p = mLruProcesses.get(i); - // If this app shouldn't be in front of the first N background - // apps, then skip over that many that are currently hidden. - if (skipTop > 0 && p.setAdj >= ProcessList.HIDDEN_APP_MIN_ADJ) { - skipTop--; - } - if (p.lruWeight <= app.lruWeight || i < bestPos) { - mLruProcesses.add(i+1, app); - break; + // Don't want to touch dependent processes that are hosting activities. + return index; + } + + int lrui = mLruProcesses.lastIndexOf(app); + if (lrui < 0) { + Log.wtf(TAG, "Adding dependent process " + app + " not on LRU list: " + + what + " " + obj + " from " + srcApp); + return index; + } + + if (lrui >= index) { + // Don't want to cause this to move dependent processes *back* in the + // list as if they were less frequently used. + return index; + } + + if (lrui >= mLruProcessActivityStart) { + // Don't want to touch dependent processes that are hosting activities. + return index; + } + + mLruProcesses.remove(lrui); + if (index > 0) { + index--; + } + mLruProcesses.add(index, app); + return index; + } + + final void removeLruProcessLocked(ProcessRecord app) { + int lrui = mLruProcesses.lastIndexOf(app); + if (lrui >= 0) { + if (lrui <= mLruProcessActivityStart) { + mLruProcessActivityStart--; } - i--; + if (lrui <= mLruProcessServiceStart) { + mLruProcessServiceStart--; + } + mLruProcesses.remove(lrui); + } + } + + final void updateLruProcessLocked(ProcessRecord app, boolean oomAdj, boolean activityChange) { + final boolean hasActivity = app.activities.size() > 0; + final boolean hasService = false; // not impl yet. app.services.size() > 0; + if (!activityChange && hasActivity) { + // The process has activties, so we are only going to allow activity-based + // adjustments move it. It should be kept in the front of the list with other + // processes that have activities, and we don't want those to change their + // order except due to activity operations. + return; + } + + mLruSeq++; + final long now = SystemClock.uptimeMillis(); + app.lastActivityTime = now; + + int lrui = mLruProcesses.lastIndexOf(app); + + if (lrui >= 0) { + if (lrui < mLruProcessActivityStart) { + mLruProcessActivityStart--; + } + if (lrui < mLruProcessServiceStart) { + mLruProcessServiceStart--; + } + mLruProcesses.remove(lrui); } - if (i < 0) { - mLruProcesses.add(0, app); + + int nextIndex; + if (hasActivity) { + // Process has activities, put it at the very tipsy-top. + mLruProcesses.add(app); + nextIndex = mLruProcessActivityStart; + } else if (hasService) { + // Process has services, put it at the top of the service list. + mLruProcesses.add(mLruProcessActivityStart, app); + nextIndex = mLruProcessServiceStart; + mLruProcessActivityStart++; + } else { + // Process not otherwise of interest, it goes to the top of the non-service area. + mLruProcesses.add(mLruProcessServiceStart, app); + nextIndex = mLruProcessServiceStart-1; + mLruProcessActivityStart++; + mLruProcessServiceStart++; } - + // If the app is currently using a content provider or service, // bump those processes as well. - if (app.connections.size() > 0) { - for (ConnectionRecord cr : app.connections) { - if (cr.binding != null && cr.binding.service != null - && cr.binding.service.app != null - && cr.binding.service.app.lruSeq != mLruSeq) { - updateLruProcessInternalLocked(cr.binding.service.app, i+1); - } + for (int j=app.connections.size()-1; j>=0; j--) { + ConnectionRecord cr = app.connections.valueAt(j); + if (cr.binding != null && !cr.serviceDead && cr.binding.service != null + && cr.binding.service.app != null + && cr.binding.service.app.lruSeq != mLruSeq) { + nextIndex = updateLruProcessInternalLocked(cr.binding.service.app, now, nextIndex, + "service connection", cr, app); } } for (int j=app.conProviders.size()-1; j>=0; j--) { ContentProviderRecord cpr = app.conProviders.get(j).provider; if (cpr.proc != null && cpr.proc.lruSeq != mLruSeq) { - updateLruProcessInternalLocked(cpr.proc, i+1); + nextIndex = updateLruProcessInternalLocked(cpr.proc, now, nextIndex, + "provider reference", cpr, app); } } - } - - final void updateLruProcessLocked(ProcessRecord app, - boolean oomAdj) { - mLruSeq++; - updateLruProcessInternalLocked(app, 0); //Slog.i(TAG, "Putting proc to front: " + app.processName); if (oomAdj) { @@ -1977,14 +2372,12 @@ public final class ActivityManagerService extends ActivityManagerNative } } - final ProcessRecord getProcessRecordLocked( - String processName, int uid) { + final ProcessRecord getProcessRecordLocked(String processName, int uid, boolean keepIfLarge) { if (uid == Process.SYSTEM_UID) { // The system gets to run in any process. If there are multiple // processes with the same uid, just pick the first (this // should never happen). - SparseArray<ProcessRecord> procs = mProcessNames.getMap().get( - processName); + SparseArray<ProcessRecord> procs = mProcessNames.getMap().get(processName); if (procs == null) return null; final int N = procs.size(); for (int i = 0; i < N; i++) { @@ -1992,6 +2385,27 @@ public final class ActivityManagerService extends ActivityManagerNative } } ProcessRecord proc = mProcessNames.get(processName, uid); + if (false && proc != null && !keepIfLarge + && proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY + && proc.lastCachedPss >= 4000) { + // Turn this condition on to cause killing to happen regularly, for testing. + if (proc.baseProcessTracker != null) { + proc.baseProcessTracker.reportCachedKill(proc.pkgList, proc.lastCachedPss); + } + killUnneededProcessLocked(proc, Long.toString(proc.lastCachedPss) + + "k from cached"); + } else if (proc != null && !keepIfLarge + && mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL + && proc.setProcState >= ActivityManager.PROCESS_STATE_CACHED_EMPTY) { + if (DEBUG_PSS) Slog.d(TAG, "May not keep " + proc + ": pss=" + proc.lastCachedPss); + if (proc.lastCachedPss >= mProcessList.getCachedRestoreThresholdKb()) { + if (proc.baseProcessTracker != null) { + proc.baseProcessTracker.reportCachedKill(proc.pkgList, proc.lastCachedPss); + } + killUnneededProcessLocked(proc, Long.toString(proc.lastCachedPss) + + "k from cached"); + } + } return proc; } @@ -2004,21 +2418,21 @@ public final class ActivityManagerService extends ActivityManagerNative } catch (RemoteException e) { } } - + boolean isNextTransitionForward() { int transit = mWindowManager.getPendingAppTransition(); return transit == AppTransition.TRANSIT_ACTIVITY_OPEN || transit == AppTransition.TRANSIT_TASK_OPEN || transit == AppTransition.TRANSIT_TASK_TO_FRONT; } - + final ProcessRecord startProcessLocked(String processName, ApplicationInfo info, boolean knownToBeDead, int intentFlags, String hostingType, ComponentName hostingName, boolean allowWhileBooting, - boolean isolated) { + boolean isolated, boolean keepIfLarge) { ProcessRecord app; if (!isolated) { - app = getProcessRecordLocked(processName, info.uid); + app = getProcessRecordLocked(processName, info.uid, keepIfLarge); } else { // If this is an isolated process, it can't re-use an existing process. app = null; @@ -2039,14 +2453,14 @@ public final class ActivityManagerService extends ActivityManagerNative // come up (we have a pid but not yet its thread), so keep it. if (DEBUG_PROCESSES) Slog.v(TAG, "App already running: " + app); // If this is a new package in the process, add the package to the list - app.addPackage(info.packageName); + app.addPackage(info.packageName, mProcessStats); return app; - } else { - // An application record is attached to a previous process, - // clean it up now. - if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG, "App died: " + app); - handleAppDiedLocked(app, true, true); } + + // An application record is attached to a previous process, + // clean it up now. + if (DEBUG_PROCESSES || DEBUG_CLEANUP) Slog.v(TAG, "App died: " + app); + handleAppDiedLocked(app, true, true); } String hostingNameStr = hostingName != null @@ -2082,7 +2496,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (app == null) { - app = newProcessRecordLocked(null, info, processName, isolated); + app = newProcessRecordLocked(info, processName, isolated); if (app == null) { Slog.w(TAG, "Failed making new process record for " + processName + "/" + info.uid + " isolated=" + isolated); @@ -2094,7 +2508,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } else { // If this is a new package in the process, add the package to the list - app.addPackage(info.packageName); + app.addPackage(info.packageName, mProcessStats); } // If the system is not ready yet, then hold off on starting this @@ -2116,7 +2530,7 @@ public final class ActivityManagerService extends ActivityManagerNative 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) { @@ -2132,10 +2546,7 @@ public final class ActivityManagerService extends ActivityManagerNative mProcessesOnHold.remove(app); updateCpuStats(); - - System.arraycopy(mProcDeaths, 0, mProcDeaths, 1, mProcDeaths.length-1); - mProcDeaths[0] = 0; - + try { int uid = app.uid; @@ -2218,16 +2629,16 @@ public final class ActivityManagerService extends ActivityManagerNative app.batteryStats.incStartsLocked(); } } - + EventLog.writeEvent(EventLogTags.AM_PROC_START, UserHandle.getUserId(uid), startResult.pid, uid, app.processName, hostingType, hostingNameStr != null ? hostingNameStr : ""); - + if (app.persistent) { Watchdog.getInstance().processStarted(app.processName, startResult.pid); } - + StringBuilder buf = mStringBuilder; buf.setLength(0); buf.append("Start proc "); @@ -2269,14 +2680,31 @@ public final class ActivityManagerService extends ActivityManagerNative } } - void updateUsageStats(ActivityRecord resumedComponent, boolean resumed) { + void updateUsageStats(ActivityRecord component, boolean resumed) { + if (DEBUG_SWITCH) Slog.d(TAG, "updateUsageStats: comp=" + component + "res=" + resumed); + final BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); if (resumed) { - mUsageStatsService.noteResumeComponent(resumedComponent.realActivity); + mUsageStatsService.noteResumeComponent(component.realActivity); + synchronized (stats) { + stats.noteActivityResumedLocked(component.app.uid); + } } else { - mUsageStatsService.notePauseComponent(resumedComponent.realActivity); + mUsageStatsService.notePauseComponent(component.realActivity); + synchronized (stats) { + stats.noteActivityPausedLocked(component.app.uid); + } } } + Intent getHomeIntent() { + Intent intent = new Intent(mTopAction, mTopData != null ? Uri.parse(mTopData) : null); + intent.setComponent(mTopComponent); + if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { + intent.addCategory(Intent.CATEGORY_HOME); + } + return intent; + } + boolean startHomeActivityLocked(int userId) { if (mHeadless) { // Added because none of the other calls to ensureBootCompleted seem to fire @@ -2292,13 +2720,7 @@ public final class ActivityManagerService extends ActivityManagerNative // error message and don't try to start anything. return false; } - Intent intent = new Intent( - mTopAction, - mTopData != null ? Uri.parse(mTopData) : null); - intent.setComponent(mTopComponent); - if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) { - intent.addCategory(Intent.CATEGORY_HOME); - } + Intent intent = getHomeIntent(); ActivityInfo aInfo = resolveActivityInfo(intent, STOCK_PM_FLAGS, userId); if (aInfo != null) { @@ -2309,11 +2731,10 @@ public final class ActivityManagerService extends ActivityManagerNative aInfo = new ActivityInfo(aInfo); aInfo.applicationInfo = getAppInfoForUser(aInfo.applicationInfo, userId); ProcessRecord app = getProcessRecordLocked(aInfo.processName, - aInfo.applicationInfo.uid); + aInfo.applicationInfo.uid, true); if (app == null || app.instrumentationClass == null) { intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_NEW_TASK); - mMainStack.startActivityLocked(null, intent, null, aInfo, - null, null, 0, 0, 0, null, 0, null, false, null); + mStackSupervisor.startHomeActivity(intent, aInfo); } } @@ -2331,7 +2752,7 @@ public final class ActivityManagerService extends ActivityManagerNative intent, intent.resolveTypeIfNeeded(mContext.getContentResolver()), flags, userId); - + if (info != null) { ai = info.activityInfo; } @@ -2351,7 +2772,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (mCheckedForSetup) { return; } - + // We will show this screen if the current one is a different // version than the last one shown, and we are not running in // low-level factory test mode. @@ -2360,12 +2781,12 @@ public final class ActivityManagerService extends ActivityManagerNative Settings.Global.getInt(resolver, Settings.Global.DEVICE_PROVISIONED, 0) != 0) { mCheckedForSetup = true; - + // See if we should be showing the platform update setup UI. Intent intent = new Intent(Intent.ACTION_UPGRADE_SETUP); List<ResolveInfo> ris = mSelf.mContext.getPackageManager() .queryIntentActivities(intent, PackageManager.GET_META_DATA); - + // We don't allow third party apps to replace this. ResolveInfo ri = null; for (int i=0; ris != null && i<ris.size(); i++) { @@ -2375,7 +2796,7 @@ public final class ActivityManagerService extends ActivityManagerNative break; } } - + if (ri != null) { String vers = ri.activityInfo.metaData != null ? ri.activityInfo.metaData.getString(Intent.METADATA_SETUP_VERSION) @@ -2390,13 +2811,13 @@ public final class ActivityManagerService extends ActivityManagerNative intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.setComponent(new ComponentName( ri.activityInfo.packageName, ri.activityInfo.name)); - mMainStack.startActivityLocked(null, intent, null, ri.activityInfo, + mStackSupervisor.startActivityLocked(null, intent, null, ri.activityInfo, null, null, 0, 0, 0, null, 0, null, false, null); } } } } - + CompatibilityInfo compatibilityInfoForPackageLocked(ApplicationInfo ai) { return mCompatModePackages.compatibilityInfoForPackageLocked(ai); } @@ -2407,6 +2828,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public int getFrontActivityScreenCompatMode() { enforceNotIsolatedCaller("getFrontActivityScreenCompatMode"); synchronized (this) { @@ -2414,6 +2836,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public void setFrontActivityScreenCompatMode(int mode) { enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY, "setFrontActivityScreenCompatMode"); @@ -2422,6 +2845,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public int getPackageScreenCompatMode(String packageName) { enforceNotIsolatedCaller("getPackageScreenCompatMode"); synchronized (this) { @@ -2429,6 +2853,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public void setPackageScreenCompatMode(String packageName, int mode) { enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY, "setPackageScreenCompatMode"); @@ -2437,6 +2862,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public boolean getPackageAskScreenCompat(String packageName) { enforceNotIsolatedCaller("getPackageAskScreenCompat"); synchronized (this) { @@ -2444,6 +2870,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public void setPackageAskScreenCompat(String packageName, boolean ask) { enforceCallingPermission(android.Manifest.permission.SET_SCREEN_COMPATIBILITY, "setPackageAskScreenCompat"); @@ -2452,11 +2879,6 @@ public final class ActivityManagerService extends ActivityManagerNative } } - void reportResumedActivityLocked(ActivityRecord r) { - //Slog.i(TAG, "**** REPORT RESUME: " + r); - updateUsageStats(r, true); - } - private void dispatchProcessesChanged() { int N; synchronized (this) { @@ -2469,6 +2891,7 @@ public final class ActivityManagerService extends ActivityManagerNative mPendingProcessChanges.clear(); if (DEBUG_PROCESS_OBSERVERS) Slog.i(TAG, "*** Delivering " + N + " process changes"); } + int i = mProcessObservers.beginBroadcast(); while (i > 0) { i--; @@ -2520,12 +2943,13 @@ public final class ActivityManagerService extends ActivityManagerNative } for (int i=0; i<N; i++) { PendingActivityLaunch pal = mPendingActivityLaunches.get(i); - mMainStack.startActivityUncheckedLocked(pal.r, pal.sourceRecord, - pal.startFlags, doResume && i == (N-1), null); + mStackSupervisor.startActivityUncheckedLocked(pal.r, pal.sourceRecord, pal.startFlags, + doResume && i == (N-1), null); } mPendingActivityLaunches.clear(); } + @Override public final int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, @@ -2535,6 +2959,7 @@ public final class ActivityManagerService extends ActivityManagerNative startFlags, profileFile, profileFd, options, UserHandle.getCallingUserId()); } + @Override public final int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, @@ -2542,11 +2967,13 @@ public final class ActivityManagerService extends ActivityManagerNative enforceNotIsolatedCaller("startActivity"); userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "startActivity", null); - return mMainStack.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, + // TODO: Switch to user app stacks here. + return mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, null, null, options, userId); } + @Override public final WaitResult startActivityAndWait(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, String profileFile, @@ -2555,12 +2982,14 @@ public final class ActivityManagerService extends ActivityManagerNative userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "startActivityAndWait", null); WaitResult res = new WaitResult(); - mMainStack.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, + // TODO: Switch to user app stacks here. + mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profileFile, profileFd, res, null, options, UserHandle.getCallingUserId()); return res; } + @Override public final int startActivityWithConfig(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, Configuration config, @@ -2568,12 +2997,14 @@ public final class ActivityManagerService extends ActivityManagerNative enforceNotIsolatedCaller("startActivityWithConfig"); userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "startActivityWithConfig", null); - int ret = mMainStack.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, - resultTo, resultWho, requestCode, startFlags, + // TODO: Switch to user app stacks here. + int ret = mStackSupervisor.startActivityMayWait(caller, -1, callingPackage, intent, + resolvedType, resultTo, resultWho, requestCode, startFlags, null, null, null, config, options, userId); return ret; } + @Override public int startActivityIntentSender(IApplicationThread caller, IntentSender intent, Intent fillInIntent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, @@ -2583,20 +3014,20 @@ public final class ActivityManagerService extends ActivityManagerNative 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 (mMainStack.mResumedActivity != null - && mMainStack.mResumedActivity.info.applicationInfo.uid == - Binder.getCallingUid()) { + final ActivityStack stack = getFocusedStack(); + if (stack.mResumedActivity != null && + stack.mResumedActivity.info.applicationInfo.uid == Binder.getCallingUid()) { mAppSwitchesAllowedTime = 0; } } @@ -2604,7 +3035,8 @@ public final class ActivityManagerService extends ActivityManagerNative resultTo, resultWho, requestCode, flagsMask, flagsValues, options); return ret; } - + + @Override public boolean startNextMatchingActivity(IBinder callingActivity, Intent intent, Bundle options) { // Refuse possible leaked file descriptors @@ -2613,7 +3045,7 @@ public final class ActivityManagerService extends ActivityManagerNative } synchronized (this) { - ActivityRecord r = mMainStack.isInStackLocked(callingActivity); + final ActivityRecord r = ActivityRecord.isInStackLocked(callingActivity); if (r == null) { ActivityOptions.abort(options); return false; @@ -2629,6 +3061,8 @@ public final class ActivityManagerService extends ActivityManagerNative // And we are resetting to find the next component... intent.setComponent(null); + final boolean debug = ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); + ActivityInfo aInfo = null; try { List<ResolveInfo> resolves = @@ -2649,6 +3083,12 @@ public final class ActivityManagerService extends ActivityManagerNative if (i<N) { aInfo = resolves.get(i).activityInfo; } + if (debug) { + Slog.v(TAG, "Next matching activity: found current " + r.packageName + + "/" + r.info.name); + Slog.v(TAG, "Next matching activity: next is " + aInfo.packageName + + "/" + aInfo.name); + } break; } } @@ -2658,6 +3098,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (aInfo == null) { // Nobody who is next! ActivityOptions.abort(options); + if (debug) Slog.d(TAG, "Next matching activity: nothing found"); return false; } @@ -2687,7 +3128,7 @@ public final class ActivityManagerService extends ActivityManagerNative } final long origId = Binder.clearCallingIdentity(); - int res = mMainStack.startActivityLocked(r.app.thread, intent, + int res = mStackSupervisor.startActivityLocked(r.app.thread, intent, r.resolvedType, aInfo, resultTo != null ? resultTo.appToken : null, resultWho, requestCode, -1, r.launchedFromUid, r.launchedFromPackage, 0, options, false, null); @@ -2708,19 +3149,22 @@ public final class ActivityManagerService extends ActivityManagerNative userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "startActivityInPackage", null); - int ret = mMainStack.startActivityMayWait(null, uid, callingPackage, intent, resolvedType, + // TODO: Switch to user app stacks here. + int ret = mStackSupervisor.startActivityMayWait(null, uid, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, null, null, null, null, options, userId); return ret; } + @Override public final int startActivities(IApplicationThread caller, String callingPackage, Intent[] intents, String[] resolvedTypes, IBinder resultTo, Bundle options, int userId) { enforceNotIsolatedCaller("startActivities"); userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "startActivity", null); - int ret = mMainStack.startActivities(caller, -1, callingPackage, intents, + // TODO: Switch to user app stacks here. + int ret = mStackSupervisor.startActivities(caller, -1, callingPackage, intents, resolvedTypes, resultTo, options, userId); return ret; } @@ -2731,7 +3175,8 @@ public final class ActivityManagerService extends ActivityManagerNative userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, true, "startActivityInPackage", null); - int ret = mMainStack.startActivities(null, uid, callingPackage, intents, resolvedTypes, + // TODO: Switch to user app stacks here. + int ret = mStackSupervisor.startActivities(null, uid, callingPackage, intents, resolvedTypes, resultTo, options, userId); return ret; } @@ -2748,6 +3193,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (task.userId == tr.userId && ((task.affinity != null && task.affinity.equals(tr.affinity)) || (task.intent != null && task.intent.filterEquals(tr.intent)))) { + tr.disposeThumbnail(); mRecentTasks.remove(i); i--; N--; @@ -2759,36 +3205,47 @@ public final class ActivityManagerService extends ActivityManagerNative } } if (N >= MAX_RECENT_TASKS) { - mRecentTasks.remove(N-1); + mRecentTasks.remove(N-1).disposeThumbnail(); } mRecentTasks.add(0, task); } - public void setRequestedOrientation(IBinder token, - int requestedOrientation) { + @Override + public void reportActivityFullyDrawn(IBinder token) { synchronized (this) { - ActivityRecord r = mMainStack.isInStackLocked(token); + ActivityRecord r = ActivityRecord.isInStackLocked(token); + if (r == null) { + return; + } + r.reportFullyDrawnLocked(); + } + } + + @Override + public void setRequestedOrientation(IBinder token, int requestedOrientation) { + synchronized (this) { + ActivityRecord r = ActivityRecord.isInStackLocked(token); if (r == null) { return; } final long origId = Binder.clearCallingIdentity(); mWindowManager.setAppOrientation(r.appToken, requestedOrientation); Configuration config = mWindowManager.updateOrientationFromAppTokens( - mConfiguration, - r.mayFreezeScreenLocked(r.app) ? r.appToken : null); + mConfiguration, r.mayFreezeScreenLocked(r.app) ? r.appToken : null); if (config != null) { r.frozenBeforeDestroy = true; if (!updateConfigurationLocked(config, r, false, false)) { - mMainStack.resumeTopActivityLocked(null); + mStackSupervisor.resumeTopActivitiesLocked(); } } Binder.restoreCallingIdentity(origId); } } + @Override public int getRequestedOrientation(IBinder token) { synchronized (this) { - ActivityRecord r = mMainStack.isInStackLocked(token); + ActivityRecord r = ActivityRecord.isInStackLocked(token); if (r == null) { return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; } @@ -2798,13 +3255,14 @@ public final class ActivityManagerService extends ActivityManagerNative /** * This is the internal entry point for handling Activity.finish(). - * + * * @param token The Binder token referencing the Activity we want to finish. * @param resultCode Result code, if any, from this Activity. * @param resultData Result data (Intent), if any, from this Activity. - * + * * @return Returns true if the activity successfully finished, or false if it is still running. */ + @Override public final boolean finishActivity(IBinder token, int resultCode, Intent resultData) { // Refuse possible leaked file descriptors if (resultData != null && resultData.hasFileDescriptors() == true) { @@ -2812,9 +3270,13 @@ public final class ActivityManagerService extends ActivityManagerNative } synchronized(this) { + ActivityRecord r = ActivityRecord.isInStackLocked(token); + if (r == null) { + return true; + } if (mController != null) { // Find the first activity that is not finishing. - ActivityRecord next = mMainStack.topRunningActivityLocked(token, 0); + ActivityRecord next = r.task.stack.topRunningActivityLocked(token, 0); if (next != null) { // ask watcher if this is allowed boolean resumeOK = true; @@ -2824,20 +3286,21 @@ public final class ActivityManagerService extends ActivityManagerNative mController = null; Watchdog.getInstance().setActivityController(null); } - + if (!resumeOK) { return false; } } } final long origId = Binder.clearCallingIdentity(); - boolean res = mMainStack.requestFinishActivityLocked(token, resultCode, + boolean res = r.task.stack.requestFinishActivityLocked(token, resultCode, resultData, "app-request", true); Binder.restoreCallingIdentity(origId); return res; } } + @Override public final void finishHeavyWeightApp() { if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) != PackageManager.PERMISSION_GRANTED) { @@ -2848,31 +3311,29 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, msg); throw new SecurityException(msg); } - + synchronized(this) { if (mHeavyWeightProcess == null) { return; } - + ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>( mHeavyWeightProcess.activities); for (int i=0; i<activities.size(); i++) { ActivityRecord r = activities.get(i); if (!r.finishing) { - int index = mMainStack.indexOfTokenLocked(r.appToken); - if (index >= 0) { - mMainStack.finishActivityLocked(r, index, Activity.RESULT_CANCELED, - null, "finish-heavy", true); - } + r.task.stack.finishActivityLocked(r, Activity.RESULT_CANCELED, + null, "finish-heavy", true); } } - + mHandler.sendMessage(mHandler.obtainMessage(CANCEL_HEAVY_NOTIFICATION_MSG, mHeavyWeightProcess.userId, 0)); mHeavyWeightProcess = null; } } - + + @Override public void crashApplication(int uid, int initialPid, String packageName, String message) { if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) @@ -2884,10 +3345,10 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, msg); throw new SecurityException(msg); } - + synchronized(this) { ProcessRecord proc = null; - + // Figure out which process to kill. We don't trust that initialPid // still has any relation to current pids, so must scan through the // list. @@ -2901,21 +3362,19 @@ public final class ActivityManagerService extends ActivityManagerNative proc = p; break; } - for (String str : p.pkgList) { - if (str.equals(packageName)) { - proc = p; - } + if (p.pkgList.containsKey(packageName)) { + proc = p; } } } - + if (proc == null) { Slog.w(TAG, "crashApplication: nothing for uid=" + uid + " initialPid=" + initialPid + " packageName=" + packageName); return; } - + if (proc.thread != null) { if (proc.pid == Process.myPid()) { Log.w(TAG, "crashApplication: trying to crash self!"); @@ -2930,61 +3389,66 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + + @Override public final void finishSubActivity(IBinder token, String resultWho, int requestCode) { synchronized(this) { final long origId = Binder.clearCallingIdentity(); - mMainStack.finishSubActivityLocked(token, resultWho, requestCode); + ActivityRecord r = ActivityRecord.isInStackLocked(token); + if (r != null) { + r.task.stack.finishSubActivityLocked(r, resultWho, requestCode); + } Binder.restoreCallingIdentity(origId); } } + @Override public boolean finishActivityAffinity(IBinder token) { synchronized(this) { final long origId = Binder.clearCallingIdentity(); - boolean res = mMainStack.finishActivityAffinityLocked(token); + ActivityRecord r = ActivityRecord.isInStackLocked(token); + boolean res = false; + if (r != null) { + res = r.task.stack.finishActivityAffinityLocked(r); + } Binder.restoreCallingIdentity(origId); return res; } } + @Override public boolean willActivityBeVisible(IBinder token) { synchronized(this) { - int i; - for (i=mMainStack.mHistory.size()-1; i>=0; i--) { - ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); - if (r.appToken == token) { - return true; - } - if (r.fullscreen && !r.finishing) { - return false; - } + ActivityStack stack = ActivityRecord.getStackLocked(token); + if (stack != null) { + return stack.willActivityBeVisibleLocked(token); } - return true; + return false; } } - + + @Override public void overridePendingTransition(IBinder token, String packageName, int enterAnim, int exitAnim) { synchronized(this) { - ActivityRecord self = mMainStack.isInStackLocked(token); + ActivityRecord self = ActivityRecord.isInStackLocked(token); if (self == null) { return; } final long origId = Binder.clearCallingIdentity(); - + if (self.state == ActivityState.RESUMED || self.state == ActivityState.PAUSING) { mWindowManager.overridePendingAppTransition(packageName, enterAnim, exitAnim, null); } - + Binder.restoreCallingIdentity(origId); } } - + /** * Main function for removing an existing process from the activity manager * as a result of that process going away. Clears out all connections @@ -2994,28 +3458,18 @@ public final class ActivityManagerService extends ActivityManagerNative boolean restarting, boolean allowRestart) { cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1); if (!restarting) { - mLruProcesses.remove(app); + removeLruProcessLocked(app); } if (mProfileProc == app) { clearProfilerLocked(); } - // Just in case... - if (mMainStack.mPausingActivity != null && mMainStack.mPausingActivity.app == app) { - if (DEBUG_PAUSE || DEBUG_CLEANUP) Slog.v(TAG, - "App died while pausing: " + mMainStack.mPausingActivity); - mMainStack.mPausingActivity = null; - } - if (mMainStack.mLastPausedActivity != null && mMainStack.mLastPausedActivity.app == app) { - mMainStack.mLastPausedActivity = null; - } - // Remove this application's activities from active lists. - boolean hasVisibleActivities = mMainStack.removeHistoryRecordsForAppLocked(app); + boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app); app.activities.clear(); - + if (app.instrumentationClass != null) { Slog.w(TAG, "Crash of app " + app.processName + " running instrumentation " + app.instrumentationClass); @@ -3025,14 +3479,14 @@ public final class ActivityManagerService extends ActivityManagerNative } if (!restarting) { - if (!mMainStack.resumeTopActivityLocked(null)) { + if (!mStackSupervisor.resumeTopActivitiesLocked()) { // If there was nothing to resume, and we are not already // restarting this process, but there is a visible activity that // is hosted by the process... then make sure all visible // activities are running, taking care of restarting this // process. if (hasVisibleActivities) { - mMainStack.ensureActivitiesVisibleLocked(null, 0); + mStackSupervisor.ensureActivitiesVisibleLocked(null, 0); } } } @@ -3060,11 +3514,69 @@ public final class ActivityManagerService extends ActivityManagerNative return appIndex >= 0 ? mLruProcesses.get(appIndex) : null; } + final void doLowMemReportIfNeededLocked(ProcessRecord dyingProc) { + // If there are no longer any background processes running, + // and the app that died was not running instrumentation, + // then tell everyone we are now low on memory. + boolean haveBg = false; + for (int i=mLruProcesses.size()-1; i>=0; i--) { + ProcessRecord rec = mLruProcesses.get(i); + if (rec.thread != null + && rec.setProcState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) { + haveBg = true; + break; + } + } + + if (!haveBg) { + boolean doReport = "1".equals(SystemProperties.get(SYSTEM_DEBUGGABLE, "0")); + if (doReport) { + long now = SystemClock.uptimeMillis(); + if (now < (mLastMemUsageReportTime+5*60*1000)) { + doReport = false; + } else { + mLastMemUsageReportTime = now; + } + } + final ArrayList<ProcessMemInfo> memInfos + = doReport ? new ArrayList<ProcessMemInfo>(mLruProcesses.size()) : null; + EventLog.writeEvent(EventLogTags.AM_LOW_MEMORY, mLruProcesses.size()); + long now = SystemClock.uptimeMillis(); + for (int i=mLruProcesses.size()-1; i>=0; i--) { + ProcessRecord rec = mLruProcesses.get(i); + if (rec == dyingProc || rec.thread == null) { + continue; + } + if (doReport) { + memInfos.add(new ProcessMemInfo(rec.processName, rec.pid, rec.setAdj, + rec.setProcState, rec.adjType, rec.makeAdjReason())); + } + if ((rec.lastLowMemory+GC_MIN_INTERVAL) <= now) { + // The low memory report is overriding any current + // state for a GC request. Make sure to do + // heavy/important/visible/foreground processes first. + if (rec.setAdj <= ProcessList.HEAVY_WEIGHT_APP_ADJ) { + rec.lastRequestedGc = 0; + } else { + rec.lastRequestedGc = rec.lastLowMemory; + } + rec.reportLowMemory = true; + rec.lastLowMemory = now; + mProcessesToGc.remove(rec); + addProcessToGcListLocked(rec); + } + } + if (doReport) { + Message msg = mHandler.obtainMessage(REPORT_MEM_USAGE_MSG, memInfos); + mHandler.sendMessage(msg); + } + scheduleAppGcsLocked(); + } + } + final void appDiedLocked(ProcessRecord app, int pid, IApplicationThread thread) { - mProcDeaths[0]++; - BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); synchronized (stats) { stats.noteProcessDiedLocked(app.info.uid, pid); @@ -3073,54 +3585,29 @@ public final class ActivityManagerService extends ActivityManagerNative // Clean up already done if the process has been re-started. if (app.pid == pid && app.thread != null && app.thread.asBinder() == thread.asBinder()) { - if (!app.killedBackground) { + boolean doLowMem = app.instrumentationClass == null; + boolean doOomAdj = doLowMem; + if (!app.killedByAm) { Slog.i(TAG, "Process " + app.processName + " (pid " + pid + ") has died."); + mAllowLowerMemLevel = true; + } else { + // Note that we always want to do oom adj to update our state with the + // new number of procs. + mAllowLowerMemLevel = false; + doLowMem = false; } EventLog.writeEvent(EventLogTags.AM_PROC_DIED, app.userId, app.pid, app.processName); if (DEBUG_CLEANUP) Slog.v( TAG, "Dying app: " + app + ", pid: " + pid + ", thread: " + thread.asBinder()); - boolean doLowMem = app.instrumentationClass == null; handleAppDiedLocked(app, false, true); + if (doOomAdj) { + updateOomAdjLocked(); + } if (doLowMem) { - // If there are no longer any background processes running, - // and the app that died was not running instrumentation, - // then tell everyone we are now low on memory. - boolean haveBg = false; - for (int i=mLruProcesses.size()-1; i>=0; i--) { - ProcessRecord rec = mLruProcesses.get(i); - if (rec.thread != null && rec.setAdj >= ProcessList.HIDDEN_APP_MIN_ADJ) { - haveBg = true; - break; - } - } - - if (!haveBg) { - EventLog.writeEvent(EventLogTags.AM_LOW_MEMORY, mLruProcesses.size()); - long now = SystemClock.uptimeMillis(); - for (int i=mLruProcesses.size()-1; i>=0; i--) { - ProcessRecord rec = mLruProcesses.get(i); - 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 - // heavy/important/visible/foreground processes first. - if (rec.setAdj <= ProcessList.HEAVY_WEIGHT_APP_ADJ) { - rec.lastRequestedGc = 0; - } else { - rec.lastRequestedGc = rec.lastLowMemory; - } - rec.reportLowMemory = true; - rec.lastLowMemory = now; - mProcessesToGc.remove(rec); - addProcessToGcListLocked(rec); - } - } - mHandler.sendEmptyMessage(REPORT_MEM_USAGE); - scheduleAppGcsLocked(); - } + doLowMemReportIfNeededLocked(app); } } else if (app.pid != pid) { // A new process has already been started. @@ -3144,7 +3631,7 @@ public final class ActivityManagerService extends ActivityManagerNative * @return file containing stack traces, or null if no dump file is configured */ public static File dumpStackTraces(boolean clearTraces, ArrayList<Integer> firstPids, - ProcessStats processStats, SparseArray<Boolean> lastPids, String[] nativeProcs) { + ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) { String tracesPath = SystemProperties.get("dalvik.vm.stack-trace-file", null); if (tracesPath == null || tracesPath.length() == 0) { return null; @@ -3169,15 +3656,16 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } - dumpStackTraces(tracesPath, firstPids, processStats, lastPids, nativeProcs); + dumpStackTraces(tracesPath, firstPids, processCpuTracker, lastPids, nativeProcs); return tracesFile; } private static void dumpStackTraces(String tracesPath, ArrayList<Integer> firstPids, - ProcessStats processStats, SparseArray<Boolean> lastPids, String[] nativeProcs) { + ProcessCpuTracker processCpuTracker, SparseArray<Boolean> lastPids, String[] nativeProcs) { // Use a FileObserver to detect when traces finish writing. // The order of traces is considered important to maintain for legibility. FileObserver observer = new FileObserver(tracesPath, FileObserver.CLOSE_WRITE) { + @Override public synchronized void onEvent(int event, String path) { notify(); } }; @@ -3200,23 +3688,23 @@ public final class ActivityManagerService extends ActivityManagerNative } // Next measure CPU usage. - if (processStats != null) { - processStats.init(); + if (processCpuTracker != null) { + processCpuTracker.init(); System.gc(); - processStats.update(); + processCpuTracker.update(); try { - synchronized (processStats) { - processStats.wait(500); // measure over 1/2 second. + synchronized (processCpuTracker) { + processCpuTracker.wait(500); // measure over 1/2 second. } } catch (InterruptedException e) { } - processStats.update(); + processCpuTracker.update(); // We'll take the stack crawls of just the top apps using CPU. - final int N = processStats.countWorkingStats(); + final int N = processCpuTracker.countWorkingStats(); int numProcs = 0; for (int i=0; i<N && numProcs<5; i++) { - ProcessStats.Stats stats = processStats.getWorkingStats(i); + ProcessCpuTracker.Stats stats = processCpuTracker.getWorkingStats(i); if (lastPids.indexOfKey(stats.pid) >= 0) { numProcs++; try { @@ -3303,7 +3791,7 @@ public final class ActivityManagerService extends ActivityManagerNative File lastTracesFile = null; File curTracesFile = null; for (int i=9; i>=0; i--) { - String name = String.format("slow%02d.txt", i); + String name = String.format(Locale.US, "slow%02d.txt", i); curTracesFile = new File(tracesDir, name); if (curTracesFile.exists()) { if (lastTracesFile != null) { @@ -3343,7 +3831,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (MONITOR_CPU_USAGE) { updateCpuStatsNow(); } - + synchronized (this) { // PowerManager.reboot() can block for a long time, so ignore ANRs while shutting down. if (mShuttingDown) { @@ -3356,7 +3844,7 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.i(TAG, "Crashing app skipping ANR: " + app + " " + annotation); return; } - + // In case we come through here for the same app before completing // this one, mark as anring now so we will bail out. app.notResponding = true; @@ -3367,11 +3855,11 @@ public final class ActivityManagerService extends ActivityManagerNative // Dump thread traces as quickly as we can, starting with "interesting" processes. firstPids.add(app.pid); - + int parentPid = app.pid; if (parent != null && parent.app != null && parent.app.pid > 0) parentPid = parent.app.pid; if (parentPid != app.pid) firstPids.add(parentPid); - + if (MY_PID != app.pid && MY_PID != parentPid) firstPids.add(MY_PID); for (int i = mLruProcesses.size() - 1; i >= 0; i--) { @@ -3397,6 +3885,7 @@ public final class ActivityManagerService extends ActivityManagerNative info.append(" (").append(activity.shortComponentName).append(")"); } info.append("\n"); + info.append("PID: ").append(app.pid).append("\n"); if (annotation != null) { info.append("Reason: ").append(annotation).append("\n"); } @@ -3404,21 +3893,21 @@ public final class ActivityManagerService extends ActivityManagerNative info.append("Parent: ").append(parent.shortComponentName).append("\n"); } - final ProcessStats processStats = new ProcessStats(true); + final ProcessCpuTracker processCpuTracker = new ProcessCpuTracker(true); - File tracesFile = dumpStackTraces(true, firstPids, processStats, lastPids, null); + File tracesFile = dumpStackTraces(true, firstPids, processCpuTracker, lastPids, null); String cpuInfo = null; if (MONITOR_CPU_USAGE) { updateCpuStatsNow(); - synchronized (mProcessStatsThread) { - cpuInfo = mProcessStats.printCurrentState(anrTime); + synchronized (mProcessCpuThread) { + cpuInfo = mProcessCpuTracker.printCurrentState(anrTime); } - info.append(processStats.printCurrentLoad()); + info.append(processCpuTracker.printCurrentLoad()); info.append(cpuInfo); } - info.append(processStats.printCurrentState(anrTime)); + info.append(processCpuTracker.printCurrentState(anrTime)); Slog.e(TAG, info.toString()); if (tracesFile == null) { @@ -3434,7 +3923,13 @@ public final class ActivityManagerService extends ActivityManagerNative // 0 == show dialog, 1 = keep waiting, -1 = kill process immediately int res = mController.appNotResponding(app.processName, app.pid, info.toString()); if (res != 0) { - if (res < 0 && app.pid != MY_PID) Process.killProcess(app.pid); + if (res < 0 && app.pid != MY_PID) { + Process.killProcess(app.pid); + } else { + synchronized (this) { + mServices.scheduleServiceTimeoutLocked(app); + } + } return; } } catch (RemoteException e) { @@ -3446,25 +3941,22 @@ public final class ActivityManagerService extends ActivityManagerNative // Unless configured otherwise, swallow ANRs in background processes & kill the process. boolean showBackground = Settings.Secure.getInt(mContext.getContentResolver(), Settings.Secure.ANR_SHOW_BACKGROUND, 0) != 0; - + synchronized (this) { if (!showBackground && !app.isInterestingToUserLocked() && app.pid != MY_PID) { - Slog.w(TAG, "Killing " + app + ": background ANR"); - EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid, - app.processName, app.setAdj, "background ANR"); - Process.killProcessQuiet(app.pid); + killUnneededProcessLocked(app, "background ANR"); return; } - + // Set the app's notResponding state, and look up the errorReportReceiver makeAppNotRespondingLocked(app, activity != null ? activity.shortComponentName : null, annotation != null ? "ANR " + annotation : "ANR", info.toString()); - + // Bring up the infamous App Not Responding dialog Message msg = Message.obtain(); - HashMap map = new HashMap(); + HashMap<String, Object> map = new HashMap<String, Object>(); msg.what = SHOW_NOT_RESPONDING_MSG; msg.obj = map; msg.arg1 = aboveSystem ? 1 : 0; @@ -3472,7 +3964,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (activity != null) { map.put("activity", activity); } - + mHandler.sendMessage(msg); } } @@ -3500,7 +3992,8 @@ public final class ActivityManagerService extends ActivityManagerNative }); } } - + + @Override public boolean clearApplicationUserData(final String packageName, final IPackageDataObserver observer, int userId) { enforceNotIsolatedCaller("clearApplicationUserData"); @@ -3532,17 +4025,21 @@ public final class ActivityManagerService extends ActivityManagerNative android.Manifest.permission.CLEAR_APP_USER_DATA, pid, uid, -1, true) == PackageManager.PERMISSION_GRANTED) { - forceStopPackageLocked(packageName, pkgUid); + forceStopPackageLocked(packageName, pkgUid, "clear data"); } else { throw new SecurityException("PID " + pid + " does not have permission " + android.Manifest.permission.CLEAR_APP_USER_DATA + " to clear data" + " of package " + packageName); } } - + try { - //clear application user data + // Clear application user data pm.clearApplicationUserData(packageName, observer, userId); + + // Remove all permissions granted from/to this package + removeUriPermissionsForPackageLocked(packageName, userId, true); + Intent intent = new Intent(Intent.ACTION_PACKAGE_DATA_CLEARED, Uri.fromParts("package", packageName, null)); intent.putExtra(Intent.EXTRA_UID, pkgUid); @@ -3556,6 +4053,7 @@ public final class ActivityManagerService extends ActivityManagerNative return true; } + @Override public void killBackgroundProcesses(final String packageName, int userId) { if (checkCallingPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) != PackageManager.PERMISSION_GRANTED && @@ -3592,6 +4090,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public void killAllBackgroundProcesses() { if (checkCallingPermission(android.Manifest.permission.KILL_BACKGROUND_PROCESSES) != PackageManager.PERMISSION_GRANTED) { @@ -3602,12 +4101,14 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, msg); throw new SecurityException(msg); } - + long callingId = Binder.clearCallingIdentity(); try { synchronized(this) { ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>(); - for (SparseArray<ProcessRecord> apps : mProcessNames.getMap().values()) { + final int NP = mProcessNames.getMap().size(); + for (int ip=0; ip<NP; ip++) { + SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip); final int NA = apps.size(); for (int ia=0; ia<NA; ia++) { ProcessRecord app = apps.valueAt(ia); @@ -3617,23 +4118,27 @@ public final class ActivityManagerService extends ActivityManagerNative } if (app.removed) { procs.add(app); - } else if (app.setAdj >= ProcessList.HIDDEN_APP_MIN_ADJ) { + } else if (app.setAdj >= ProcessList.CACHED_APP_MIN_ADJ) { app.removed = true; procs.add(app); } } } - + int N = procs.size(); for (int i=0; i<N; i++) { removeProcessLocked(procs.get(i), false, true, "kill all background"); } + mAllowLowerMemLevel = true; + updateOomAdjLocked(); + doLowMemReportIfNeededLocked(null); } } finally { Binder.restoreCallingIdentity(callingId); } } + @Override public void forceStopPackage(final String packageName, int userId) { if (checkCallingPermission(android.Manifest.permission.FORCE_STOP_PACKAGES) != PackageManager.PERMISSION_GRANTED) { @@ -3644,7 +4149,8 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, msg); throw new SecurityException(msg); } - userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), + final int callingPid = Binder.getCallingPid(); + userId = handleIncomingUser(callingPid, Binder.getCallingUid(), userId, true, true, "forceStopPackage", null); long callingId = Binder.clearCallingIdentity(); try { @@ -3670,7 +4176,7 @@ public final class ActivityManagerService extends ActivityManagerNative + packageName + ": " + e); } if (isUserRunningLocked(user, false)) { - forceStopPackageLocked(packageName, pkgUid); + forceStopPackageLocked(packageName, pkgUid, "from pid " + callingPid); } } } @@ -3682,7 +4188,8 @@ public final class ActivityManagerService extends ActivityManagerNative /* * The pkg name and app id have to be specified. */ - public void killApplicationWithAppId(String pkg, int appid) { + @Override + public void killApplicationWithAppId(String pkg, int appid, String reason) { if (pkg == null) { return; } @@ -3698,7 +4205,10 @@ public final class ActivityManagerService extends ActivityManagerNative Message msg = mHandler.obtainMessage(KILL_APPLICATION_MSG); msg.arg1 = appid; msg.arg2 = 0; - msg.obj = pkg; + Bundle bundle = new Bundle(); + bundle.putString("pkg", pkg); + bundle.putString("reason", reason); + msg.obj = bundle; mHandler.sendMessage(msg); } else { throw new SecurityException(callerUid + " cannot kill pkg: " + @@ -3706,6 +4216,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public void closeSystemDialogs(String reason) { enforceNotIsolatedCaller("closeSystemDialogs"); @@ -3743,39 +4254,69 @@ public final class ActivityManagerService extends ActivityManagerNative } mWindowManager.closeSystemDialogs(reason); - for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { - ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); - if ((r.info.flags&ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0) { - r.stack.finishActivityLocked(r, i, - Activity.RESULT_CANCELED, null, "close-sys", true); - } - } + mStackSupervisor.closeSystemDialogsLocked(); broadcastIntentLocked(null, null, intent, null, null, 0, null, null, null, AppOpsManager.OP_NONE, false, false, -1, Process.SYSTEM_UID, UserHandle.USER_ALL); } - public Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids) - throws RemoteException { + @Override + public Debug.MemoryInfo[] getProcessMemoryInfo(int[] pids) { enforceNotIsolatedCaller("getProcessMemoryInfo"); Debug.MemoryInfo[] infos = new Debug.MemoryInfo[pids.length]; for (int i=pids.length-1; i>=0; i--) { + ProcessRecord proc; + int oomAdj; + synchronized (this) { + synchronized (mPidsSelfLocked) { + proc = mPidsSelfLocked.get(pids[i]); + oomAdj = proc != null ? proc.setAdj : 0; + } + } infos[i] = new Debug.MemoryInfo(); Debug.getMemoryInfo(pids[i], infos[i]); + if (proc != null) { + synchronized (this) { + if (proc.thread != null && proc.setAdj == oomAdj) { + // Record this for posterity if the process has been stable. + proc.baseProcessTracker.addPss(infos[i].getTotalPss(), + infos[i].getTotalUss(), false, proc.pkgList); + } + } + } } return infos; } - public long[] getProcessPss(int[] pids) throws RemoteException { + @Override + public long[] getProcessPss(int[] pids) { enforceNotIsolatedCaller("getProcessPss"); long[] pss = new long[pids.length]; for (int i=pids.length-1; i>=0; i--) { - pss[i] = Debug.getPss(pids[i]); + ProcessRecord proc; + int oomAdj; + synchronized (this) { + synchronized (mPidsSelfLocked) { + proc = mPidsSelfLocked.get(pids[i]); + oomAdj = proc != null ? proc.setAdj : 0; + } + } + long[] tmpUss = new long[1]; + pss[i] = Debug.getPss(pids[i], tmpUss); + if (proc != null) { + synchronized (this) { + if (proc.thread != null && proc.setAdj == oomAdj) { + // Record this for posterity if the process has been stable. + proc.baseProcessTracker.addPss(pss[i], tmpUss[0], false, proc.pkgList); + } + } + } } return pss; } + @Override public void killApplicationProcess(String processName, int uid) { if (processName == null) { return; @@ -3785,7 +4326,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Only the system server can kill an application if (callerUid == Process.SYSTEM_UID) { synchronized (this) { - ProcessRecord app = getProcessRecordLocked(processName, uid); + ProcessRecord app = getProcessRecordLocked(processName, uid, true); if (app != null && app.thread != null) { try { app.thread.scheduleSuicide(); @@ -3803,9 +4344,9 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private void forceStopPackageLocked(final String packageName, int uid) { + private void forceStopPackageLocked(final String packageName, int uid, String reason) { forceStopPackageLocked(packageName, UserHandle.getAppId(uid), false, - false, true, false, UserHandle.getUserId(uid)); + false, true, false, UserHandle.getUserId(uid), reason); Intent intent = new Intent(Intent.ACTION_PACKAGE_RESTARTED, Uri.fromParts("package", packageName, null)); if (!mProcessesReady) { @@ -3820,8 +4361,8 @@ public final class ActivityManagerService extends ActivityManagerNative MY_PID, Process.SYSTEM_UID, UserHandle.getUserId(uid)); } - private void forceStopUserLocked(int userId) { - forceStopPackageLocked(null, -1, false, false, true, false, userId); + private void forceStopUserLocked(int userId, String reason) { + forceStopPackageLocked(null, -1, false, false, true, false, userId, reason); Intent intent = new Intent(Intent.ACTION_USER_STOPPED); intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY | Intent.FLAG_RECEIVER_FOREGROUND); @@ -3841,7 +4382,9 @@ public final class ActivityManagerService extends ActivityManagerNative // same UID (except for the system or root user), and all whose name // matches the package name. final String procNamePrefix = packageName != null ? (packageName + ":") : null; - for (SparseArray<ProcessRecord> apps : mProcessNames.getMap().values()) { + final int NP = mProcessNames.getMap().size(); + for (int ip=0; ip<NP; ip++) { + SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip); final int NA = apps.size(); for (int ia=0; ia<NA; ia++) { ProcessRecord app = apps.valueAt(ia); @@ -3880,7 +4423,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (userId != UserHandle.USER_ALL && app.userId != userId) { continue; } - if (!app.pkgList.contains(packageName)) { + if (!app.pkgList.containsKey(packageName)) { continue; } } @@ -3893,17 +4436,18 @@ public final class ActivityManagerService extends ActivityManagerNative procs.add(app); } } - + int N = procs.size(); for (int i=0; i<N; i++) { removeProcessLocked(procs.get(i), callerWillRestart, allowRestart, reason); } + updateOomAdjLocked(); return N > 0; } private final boolean forceStopPackageLocked(String name, int appId, boolean callerWillRestart, boolean purgeCache, boolean doit, - boolean evenPersistent, int userId) { + boolean evenPersistent, int userId, String reason) { int i; int N; @@ -3921,15 +4465,15 @@ public final class ActivityManagerService extends ActivityManagerNative if (doit) { if (name != null) { - Slog.i(TAG, "Force stopping package " + name + " appid=" + appId - + " user=" + userId); + Slog.i(TAG, "Force stopping " + name + " appid=" + appId + + " user=" + userId + ": " + reason); } else { - Slog.i(TAG, "Force stopping user " + userId); + Slog.i(TAG, "Force stopping u" + userId + ": " + reason); } - Iterator<SparseArray<Long>> badApps = mProcessCrashTimes.getMap().values().iterator(); - while (badApps.hasNext()) { - SparseArray<Long> ba = badApps.next(); + final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap(); + for (int ip=pmap.size()-1; ip>=0; ip--) { + SparseArray<Long> ba = pmap.valueAt(ip); for (i=ba.size()-1; i>=0; i--) { boolean remove = false; final int entUid = ba.keyAt(i); @@ -3951,45 +4495,20 @@ public final class ActivityManagerService extends ActivityManagerNative } } if (ba.size() == 0) { - badApps.remove(); + pmap.removeAt(ip); } } } boolean didSomething = killPackageProcessesLocked(name, appId, userId, -100, callerWillRestart, true, doit, evenPersistent, - name == null ? ("force stop user " + userId) : ("force stop " + name)); - - TaskRecord lastTask = null; - for (i=0; i<mMainStack.mHistory.size(); i++) { - ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); - final boolean samePackage = r.packageName.equals(name) - || (name == null && r.userId == userId); - if ((userId == UserHandle.USER_ALL || r.userId == userId) - && (samePackage || r.task == lastTask) - && (r.app == null || evenPersistent || !r.app.persistent)) { - if (!doit) { - if (r.finishing) { - // If this activity is just finishing, then it is not - // interesting as far as something to stop. - continue; - } - return true; - } - didSomething = true; - Slog.i(TAG, " Force finishing activity " + r); - if (samePackage) { - if (r.app != null) { - r.app.removed = true; - } - r.app = null; - } - lastTask = r.task; - if (r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "force-stop", true)) { - i--; - } + name == null ? ("stop user " + userId) : ("stop " + name)); + + if (mStackSupervisor.forceStopPackageLocked(name, doit, evenPersistent, userId)) { + if (!doit) { + return true; } + didSomething = true; } if (mServices.forceStopLocked(name, userId, evenPersistent, doit)) { @@ -4017,6 +4536,9 @@ public final class ActivityManagerService extends ActivityManagerNative removeDyingProviderLocked(null, providers.get(i), true); } + // Remove transient permissions granted from/to this package/user + removeUriPermissionsForPackageLocked(name, userId, false); + if (name == null) { // Remove pending intents. For now we only do this when force // stopping users, because we have some problems when doing this @@ -4077,11 +4599,11 @@ public final class ActivityManagerService extends ActivityManagerNative } } if (mBooted) { - mMainStack.resumeTopActivityLocked(null); - mMainStack.scheduleIdleLocked(); + mStackSupervisor.resumeTopActivitiesLocked(); + mStackSupervisor.scheduleIdleLocked(); } } - + return didSomething; } @@ -4107,11 +4629,10 @@ public final class ActivityManagerService extends ActivityManagerNative mPidsSelfLocked.remove(pid); mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); } - Slog.i(TAG, "Killing proc " + app.toShortString() + ": " + reason); + killUnneededProcessLocked(app, reason); handleAppDiedLocked(app, true, allowRestart); - mLruProcesses.remove(app); - Process.killProcessQuiet(pid); - + removeLruProcessLocked(app); + if (app.persistent && !app.isolated) { if (!callerWillRestart) { addAppLocked(app.info, false); @@ -4122,7 +4643,7 @@ public final class ActivityManagerService extends ActivityManagerNative } else { mRemovedProcesses.add(app); } - + return needRestart; } @@ -4134,9 +4655,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (knownApp != null && knownApp.thread == null) { mPidsSelfLocked.remove(pid); gone = true; - } + } } - + if (gone) { Slog.w(TAG, "Process " + app + " failed to attach"); EventLog.writeEvent(EventLogTags.AM_PROCESS_START_TIMEOUT, app.userId, @@ -4152,9 +4673,7 @@ public final class ActivityManagerService extends ActivityManagerNative checkAppInLaunchingProvidersLocked(app, true); // Take care of any services that are waiting for the process. mServices.processStartTimedOutLocked(app); - EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, pid, - app.processName, app.setAdj, "start timeout"); - Process.killProcessQuiet(pid); + killUnneededProcessLocked(app, "start timeout"); if (mBackupTarget != null && mBackupTarget.app.pid == pid) { Slog.w(TAG, "Unattached app died before backup, skipping"); try { @@ -4216,38 +4735,38 @@ public final class ActivityManagerService extends ActivityManagerNative if (localLOGV) Slog.v( TAG, "Binding process pid " + pid + " to record " + app); - String processName = app.processName; + final String processName = app.processName; try { AppDeathRecipient adr = new AppDeathRecipient( app, pid, thread); thread.asBinder().linkToDeath(adr, 0); app.deathRecipient = adr; } catch (RemoteException e) { - app.resetPackageList(); + app.resetPackageList(mProcessStats); startProcessLocked(app, "link fail", processName); return false; } EventLog.writeEvent(EventLogTags.AM_PROC_BOUND, app.userId, app.pid, app.processName); - - app.thread = thread; + + app.makeActive(thread, mProcessStats); app.curAdj = app.setAdj = -100; - app.curSchedGroup = Process.THREAD_GROUP_DEFAULT; - app.setSchedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; + app.curSchedGroup = app.setSchedGroup = Process.THREAD_GROUP_DEFAULT; app.forcingToForeground = null; app.foregroundServices = false; app.hasShownUi = false; app.debugging = false; + app.cached = false; mHandler.removeMessages(PROC_START_TIMEOUT_MSG, app); boolean normalMode = mProcessesReady || isAllowedWhileBooting(app.info); - List providers = normalMode ? generateApplicationProvidersLocked(app) : null; + List<ProviderInfo> providers = normalMode ? generateApplicationProvidersLocked(app) : null; if (!normalMode) { Slog.i(TAG, "Launching preboot mode app: " + app); } - + if (localLOGV) Slog.v( TAG, "New app record " + app + " thread=" + thread.asBinder() + " pid=" + pid); @@ -4285,7 +4804,7 @@ public final class ActivityManagerService extends ActivityManagerNative || (mBackupTarget.backupMode == BackupRecord.RESTORE_FULL) || (mBackupTarget.backupMode == BackupRecord.BACKUP_FULL); } - + ensurePackageDexOpt(app.instrumentationInfo != null ? app.instrumentationInfo.packageName : app.info.packageName); @@ -4307,7 +4826,7 @@ public final class ActivityManagerService extends ActivityManagerNative isRestrictedBackupMode || !normalMode, app.persistent, new Configuration(mConfiguration), app.compat, getCommonServicesLocked(), mCoreSettingsObserver.getCoreSettingsLocked()); - updateLruProcessLocked(app, false); + updateLruProcessLocked(app, false, false); app.lastRequestedGc = app.lastLowMemory = SystemClock.uptimeMillis(); } catch (Exception e) { // todo: Yikes! What should we do? For now we will try to @@ -4315,7 +4834,7 @@ public final class ActivityManagerService extends ActivityManagerNative // an infinite loop of restarting processes... Slog.w(TAG, "Exception thrown during bind!", e); - app.resetPackageList(); + app.resetPackageList(mProcessStats); app.unlinkDeathRecipient(); startProcessLocked(app, "bind fail", processName); return false; @@ -4331,23 +4850,13 @@ public final class ActivityManagerService extends ActivityManagerNative boolean didSomething = false; // See if the top visible activity is waiting to run in this process... - ActivityRecord hr = mMainStack.topRunningActivityLocked(null); - if (hr != null && normalMode) { - if (hr.app == null && app.uid == hr.info.applicationInfo.uid - && processName.equals(hr.processName)) { - try { - if (mHeadless) { - Slog.e(TAG, "Starting activities not supported on headless device: " + hr); - } else if (mMainStack.realStartActivityLocked(hr, app, true, true)) { - didSomething = true; - } - } catch (Exception e) { - Slog.w(TAG, "Exception in new application when starting activity " - + hr.intent.getComponent().flattenToShortString(), e); - badApp = true; + if (normalMode) { + try { + if (mStackSupervisor.attachApplicationLocked(app, mHeadless)) { + didSomething = true; } - } else { - mMainStack.ensureActivitiesVisibleLocked(hr, null, processName, 0); + } catch (Exception e) { + badApp = true; } } @@ -4363,7 +4872,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Check if a next-broadcast receiver is in this process... if (!badApp && isPendingBroadcastProcessLocked(pid)) { try { - didSomething = sendPendingBroadcastsLocked(app); + didSomething |= sendPendingBroadcastsLocked(app); } catch (Exception e) { // If the app died trying to launch the receiver we declare it 'bad' badApp = true; @@ -4398,6 +4907,7 @@ public final class ActivityManagerService extends ActivityManagerNative return true; } + @Override public final void attachApplication(IApplicationThread thread) { synchronized (this) { int callingPid = Binder.getCallingPid(); @@ -4407,13 +4917,16 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public final void activityIdle(IBinder token, Configuration config, boolean stopProfiling) { final long origId = Binder.clearCallingIdentity(); - ActivityRecord r = mMainStack.activityIdleInternal(token, false, config); - if (stopProfiling) { - synchronized (this) { - if (mProfileProc == r.app) { - if (mProfileFd != null) { + synchronized (this) { + ActivityStack stack = ActivityRecord.getStackLocked(token); + if (stack != null) { + ActivityRecord r = + mStackSupervisor.activityIdleInternalLocked(token, false, config); + if (stopProfiling) { + if ((mProfileProc == r.app) && (mProfileFd != null)) { try { mProfileFd.close(); } catch (IOException e) { @@ -4436,21 +4949,24 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public void showBootMessage(final CharSequence msg, final boolean always) { enforceNotIsolatedCaller("showBootMessage"); mWindowManager.showBootMessage(msg, always); } + @Override public void dismissKeyguardOnNextActivity() { enforceNotIsolatedCaller("dismissKeyguardOnNextActivity"); final long token = Binder.clearCallingIdentity(); try { synchronized (this) { + if (DEBUG_LOCKSCREEN) logLockScreen(""); if (mLockScreenShown) { mLockScreenShown = false; comeOutOfSleepIfNeededLocked(); } - mMainStack.dismissKeyguardOnNextActivityLocked(); + mStackSupervisor.setDismissKeyguard(true); } } finally { Binder.restoreCallingIdentity(token); @@ -4468,7 +4984,8 @@ public final class ActivityManagerService extends ActivityManagerNative if (pkgs != null) { for (String pkg : pkgs) { synchronized (ActivityManagerService.this) { - if (forceStopPackageLocked(pkg, -1, false, false, false, false, 0)) { + if (forceStopPackageLocked(pkg, -1, false, false, false, false, 0, + "finished booting")) { setResultCode(Activity.RESULT_OK); return; } @@ -4506,10 +5023,22 @@ public final class ActivityManagerService extends ActivityManagerNative final int userId = mStartedUsers.keyAt(i); Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null); intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); - broadcastIntentLocked(null, null, intent, - null, null, 0, null, null, + intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT); + broadcastIntentLocked(null, null, intent, null, + new IIntentReceiver.Stub() { + @Override + public void performReceive(Intent intent, int resultCode, + String data, Bundle extras, boolean ordered, + boolean sticky, int sendingUser) { + synchronized (ActivityManagerService.this) { + requestPssAllProcsLocked(SystemClock.uptimeMillis(), + true, false); + } + } + }, + 0, null, null, android.Manifest.permission.RECEIVE_BOOT_COMPLETED, - AppOpsManager.OP_NONE, false, false, MY_PID, Process.SYSTEM_UID, + AppOpsManager.OP_NONE, true, false, MY_PID, Process.SYSTEM_UID, userId); } } @@ -4536,18 +5065,31 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public final void activityResumed(IBinder token) { final long origId = Binder.clearCallingIdentity(); - mMainStack.activityResumed(token); + synchronized(this) { + ActivityStack stack = ActivityRecord.getStackLocked(token); + if (stack != null) { + ActivityRecord.activityResumedLocked(token); + } + } Binder.restoreCallingIdentity(origId); } + @Override public final void activityPaused(IBinder token) { final long origId = Binder.clearCallingIdentity(); - mMainStack.activityPaused(token, false); + synchronized(this) { + ActivityStack stack = ActivityRecord.getStackLocked(token); + if (stack != null) { + stack.activityPausedLocked(token, false); + } + } Binder.restoreCallingIdentity(origId); } + @Override public final void activityStopped(IBinder token, Bundle icicle, Bitmap thumbnail, CharSequence description) { if (localLOGV) Slog.v( @@ -4563,9 +5105,9 @@ public final class ActivityManagerService extends ActivityManagerNative final long origId = Binder.clearCallingIdentity(); synchronized (this) { - r = mMainStack.isInStackLocked(token); + r = ActivityRecord.isInStackLocked(token); if (r != null) { - r.stack.activityStoppedLocked(r, icicle, thumbnail, description); + r.task.stack.activityStoppedLocked(r, icicle, thumbnail, description); } } @@ -4578,11 +5120,18 @@ public final class ActivityManagerService extends ActivityManagerNative Binder.restoreCallingIdentity(origId); } + @Override public final void activityDestroyed(IBinder token) { if (DEBUG_SWITCH) Slog.v(TAG, "ACTIVITY DESTROYED: " + token); - mMainStack.activityDestroyed(token); + synchronized (this) { + ActivityStack stack = ActivityRecord.getStackLocked(token); + if (stack != null) { + stack.activityDestroyedLocked(token); + } + } } + @Override public String getCallingPackage(IBinder token) { synchronized (this) { ActivityRecord r = getCallingRecordLocked(token); @@ -4590,6 +5139,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public ComponentName getCallingActivity(IBinder token) { synchronized (this) { ActivityRecord r = getCallingRecordLocked(token); @@ -4598,16 +5148,17 @@ public final class ActivityManagerService extends ActivityManagerNative } private ActivityRecord getCallingRecordLocked(IBinder token) { - ActivityRecord r = mMainStack.isInStackLocked(token); + ActivityRecord r = ActivityRecord.isInStackLocked(token); if (r == null) { return null; } return r.resultTo; } + @Override public ComponentName getActivityClassForToken(IBinder token) { synchronized(this) { - ActivityRecord r = mMainStack.isInStackLocked(token); + ActivityRecord r = ActivityRecord.isInStackLocked(token); if (r == null) { return null; } @@ -4615,9 +5166,10 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public String getPackageForToken(IBinder token) { synchronized(this) { - ActivityRecord r = mMainStack.isInStackLocked(token); + ActivityRecord r = ActivityRecord.isInStackLocked(token); if (r == null) { return null; } @@ -4625,6 +5177,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public IIntentSender getIntentSender(int type, String packageName, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, @@ -4695,7 +5248,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + IIntentSender getIntentSenderLocked(int type, String packageName, int callingUid, int userId, IBinder token, String resultWho, int requestCode, Intent[] intents, String[] resolvedTypes, int flags, @@ -4704,7 +5257,7 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.v(TAG_MU, "getIntentSenderLocked(): uid=" + callingUid); ActivityRecord activity = null; if (type == ActivityManager.INTENT_SENDER_ACTIVITY_RESULT) { - activity = mMainStack.isInStackLocked(token); + activity = ActivityRecord.isInStackLocked(token); if (activity == null) { return null; } @@ -4761,6 +5314,7 @@ public final class ActivityManagerService extends ActivityManagerNative return rec; } + @Override public void cancelIntentSender(IIntentSender sender) { if (!(sender instanceof PendingIntentRecord)) { return; @@ -4794,6 +5348,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public String getPackageForIntentSender(IIntentSender pendingResult) { if (!(pendingResult instanceof PendingIntentRecord)) { return null; @@ -4806,6 +5361,7 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } + @Override public int getUidForIntentSender(IIntentSender sender) { if (sender instanceof PendingIntentRecord) { try { @@ -4817,6 +5373,7 @@ public final class ActivityManagerService extends ActivityManagerNative return -1; } + @Override public boolean isIntentSenderTargetedToPackage(IIntentSender pendingResult) { if (!(pendingResult instanceof PendingIntentRecord)) { return false; @@ -4838,6 +5395,7 @@ public final class ActivityManagerService extends ActivityManagerNative return false; } + @Override public boolean isIntentSenderAnActivity(IIntentSender pendingResult) { if (!(pendingResult instanceof PendingIntentRecord)) { return false; @@ -4853,6 +5411,7 @@ public final class ActivityManagerService extends ActivityManagerNative return false; } + @Override public Intent getIntentForIntentSender(IIntentSender pendingResult) { if (!(pendingResult instanceof PendingIntentRecord)) { return null; @@ -4865,16 +5424,18 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } + @Override public void setProcessLimit(int max) { enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT, "setProcessLimit()"); synchronized (this) { - mProcessLimit = max < 0 ? ProcessList.MAX_HIDDEN_APPS : max; + mProcessLimit = max < 0 ? ProcessList.MAX_CACHED_APPS : max; mProcessLimitOverride = max; } trimApplications(); } + @Override public int getProcessLimit() { synchronized (this) { return mProcessLimitOverride; @@ -4900,7 +5461,8 @@ public final class ActivityManagerService extends ActivityManagerNative updateOomAdjLocked(); } } - + + @Override public void setProcessForeground(IBinder token, int pid, boolean isForeground) { enforceCallingPermission(android.Manifest.permission.SET_PROCESS_LIMIT, "setProcessForeground()"); @@ -4924,6 +5486,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (isForeground && token != null) { ForegroundToken newToken = new ForegroundToken() { + @Override public void binderDied() { foregroundTokenDied(this); } @@ -4958,6 +5521,7 @@ public final class ActivityManagerService extends ActivityManagerNative mActivityManagerService = activityManagerService; } + @Override public boolean checkPermission(String permission, int pid, int uid) { return mActivityManagerService.checkPermission(permission, pid, uid) == PackageManager.PERMISSION_GRANTED; @@ -4965,12 +5529,14 @@ public final class ActivityManagerService extends ActivityManagerNative } class IntentFirewallInterface implements IntentFirewall.AMSInterface { + @Override public int checkComponentPermission(String permission, int pid, int uid, int owningUid, boolean exported) { return ActivityManagerService.this.checkComponentPermission(permission, pid, uid, owningUid, exported); } + @Override public Object getAMSLock() { return ActivityManagerService.this; } @@ -5009,6 +5575,7 @@ public final class ActivityManagerService extends ActivityManagerNative * * This can be called with or without the global lock held. */ + @Override public int checkPermission(String permission, int pid, int uid) { if (permission == null) { return PackageManager.PERMISSION_DENIED; @@ -5130,19 +5697,61 @@ public final class ActivityManagerService extends ActivityManagerNative return readMet && writeMet; } - private final boolean checkUriPermissionLocked(Uri uri, int uid, - int modeFlags) { + private ProviderInfo getProviderInfoLocked(String authority, int userHandle) { + ProviderInfo pi = null; + ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userHandle); + if (cpr != null) { + pi = cpr.info; + } else { + try { + pi = AppGlobals.getPackageManager().resolveContentProvider( + authority, PackageManager.GET_URI_PERMISSION_PATTERNS, userHandle); + } catch (RemoteException ex) { + } + } + return pi; + } + + private UriPermission findUriPermissionLocked(int targetUid, Uri uri) { + ArrayMap<Uri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid); + if (targetUris != null) { + return targetUris.get(uri); + } else { + return null; + } + } + + private UriPermission findOrCreateUriPermissionLocked( + String sourcePkg, String targetPkg, int targetUid, Uri uri) { + ArrayMap<Uri, UriPermission> targetUris = mGrantedUriPermissions.get(targetUid); + if (targetUris == null) { + targetUris = Maps.newArrayMap(); + mGrantedUriPermissions.put(targetUid, targetUris); + } + + UriPermission perm = targetUris.get(uri); + if (perm == null) { + perm = new UriPermission(sourcePkg, targetPkg, targetUid, uri); + targetUris.put(uri, perm); + } + + return perm; + } + + private final boolean checkUriPermissionLocked( + Uri uri, int uid, int modeFlags, int minStrength) { // Root gets to do everything. if (uid == 0) { return true; } - HashMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(uid); + ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(uid); if (perms == null) return false; UriPermission perm = perms.get(uri); if (perm == null) return false; - return (modeFlags&perm.modeFlags) == modeFlags; + return perm.getStrength(modeFlags) >= minStrength; } + @Override public int checkUriPermission(Uri uri, int pid, int uid, int modeFlags) { enforceNotIsolatedCaller("checkUriPermission"); @@ -5159,7 +5768,7 @@ public final class ActivityManagerService extends ActivityManagerNative return PackageManager.PERMISSION_GRANTED; } synchronized(this) { - return checkUriPermissionLocked(uri, uid, modeFlags) + return checkUriPermissionLocked(uri, uid, modeFlags, UriPermission.STRENGTH_OWNED) ? PackageManager.PERMISSION_GRANTED : PackageManager.PERMISSION_DENIED; } @@ -5176,6 +5785,7 @@ public final class ActivityManagerService extends ActivityManagerNative */ int checkGrantUriPermissionLocked(int callingUid, String targetPkg, Uri uri, int modeFlags, int lastTargetUid) { + final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0; modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); if (modeFlags == 0) { @@ -5196,20 +5806,8 @@ public final class ActivityManagerService extends ActivityManagerNative return -1; } - String name = uri.getAuthority(); - ProviderInfo pi = null; - ContentProviderRecord cpr = mProviderMap.getProviderByName(name, - UserHandle.getUserId(callingUid)); - if (cpr != null) { - pi = cpr.info; - } else { - try { - pi = pm.resolveContentProvider(name, - PackageManager.GET_URI_PERMISSION_PATTERNS, - UserHandle.getUserId(callingUid)); - } catch (RemoteException ex) { - } - } + final String authority = uri.getAuthority(); + final ProviderInfo pi = getProviderInfoLocked(authority, UserHandle.getUserId(callingUid)); if (pi == null) { Slog.w(TAG, "No content provider found for permission check: " + uri.toSafeString()); return -1; @@ -5284,7 +5882,10 @@ public final class ActivityManagerService extends ActivityManagerNative // this uri? if (callingUid != Process.myUid()) { if (!checkHoldingPermissionsLocked(pm, pi, uri, callingUid, modeFlags)) { - if (!checkUriPermissionLocked(uri, callingUid, modeFlags)) { + // Require they hold a strong enough Uri permission + final int minStrength = persistable ? UriPermission.STRENGTH_PERSISTABLE + : UriPermission.STRENGTH_OWNED; + if (!checkUriPermissionLocked(uri, callingUid, modeFlags, minStrength)) { throw new SecurityException("Uid " + callingUid + " does not have permission to uri " + uri); } @@ -5294,6 +5895,7 @@ public final class ActivityManagerService extends ActivityManagerNative return targetUid; } + @Override public int checkGrantUriPermission(int callingUid, String targetPkg, Uri uri, int modeFlags) { enforceNotIsolatedCaller("checkGrantUriPermission"); @@ -5302,8 +5904,9 @@ public final class ActivityManagerService extends ActivityManagerNative } } - void grantUriPermissionUncheckedLocked(int targetUid, String targetPkg, - Uri uri, int modeFlags, UriPermissionOwner owner) { + void grantUriPermissionUncheckedLocked( + int targetUid, String targetPkg, Uri uri, int modeFlags, UriPermissionOwner owner) { + final boolean persistable = (modeFlags & Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION) != 0; modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); if (modeFlags == 0) { @@ -5316,33 +5919,17 @@ public final class ActivityManagerService extends ActivityManagerNative if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Granting " + targetPkg + "/" + targetUid + " permission to " + uri); - - HashMap<Uri, UriPermission> targetUris - = mGrantedUriPermissions.get(targetUid); - if (targetUris == null) { - targetUris = new HashMap<Uri, UriPermission>(); - mGrantedUriPermissions.put(targetUid, targetUris); - } - UriPermission perm = targetUris.get(uri); - if (perm == null) { - perm = new UriPermission(targetUid, uri); - targetUris.put(uri, perm); + final String authority = uri.getAuthority(); + final ProviderInfo pi = getProviderInfoLocked(authority, UserHandle.getUserId(targetUid)); + if (pi == null) { + Slog.w(TAG, "No content provider found for grant: " + uri.toSafeString()); + return; } - perm.modeFlags |= modeFlags; - if (owner == null) { - perm.globalModeFlags |= modeFlags; - } else { - if ((modeFlags&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) { - perm.readOwners.add(owner); - owner.addReadPermission(perm); - } - if ((modeFlags&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) { - perm.writeOwners.add(owner); - owner.addWritePermission(perm); - } - } + final UriPermission perm = findOrCreateUriPermissionLocked( + pi.packageName, targetPkg, targetUid, uri); + perm.grantModes(modeFlags, persistable, owner); } void grantUriPermissionLocked(int callingUid, String targetPkg, Uri uri, @@ -5364,10 +5951,10 @@ public final class ActivityManagerService extends ActivityManagerNative final int targetUid; final int flags; - NeededUriGrants(String _targetPkg, int _targetUid, int _flags) { - targetPkg = _targetPkg; - targetUid = _targetUid; - flags = _flags; + NeededUriGrants(String targetPkg, int targetUid, int flags) { + this.targetPkg = targetPkg; + this.targetUid = targetUid; + this.flags = flags; } } @@ -5394,12 +5981,13 @@ public final class ActivityManagerService extends ActivityManagerNative if (data == null && clip == null) { return null; } + if (data != null) { - int target = checkGrantUriPermissionLocked(callingUid, targetPkg, data, + int targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, data, mode, needed != null ? needed.targetUid : -1); - if (target > 0) { + if (targetUid > 0) { if (needed == null) { - needed = new NeededUriGrants(targetPkg, target, mode); + needed = new NeededUriGrants(targetPkg, targetUid, mode); } needed.add(data); } @@ -5408,12 +5996,12 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=0; i<clip.getItemCount(); i++) { Uri uri = clip.getItemAt(i).getUri(); if (uri != null) { - int target = -1; - target = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, + int targetUid = -1; + targetUid = checkGrantUriPermissionLocked(callingUid, targetPkg, uri, mode, needed != null ? needed.targetUid : -1); - if (target > 0) { + if (targetUid > 0) { if (needed == null) { - needed = new NeededUriGrants(targetPkg, target, mode); + needed = new NeededUriGrants(targetPkg, targetUid, mode); } needed.add(uri); } @@ -5457,6 +6045,7 @@ public final class ActivityManagerService extends ActivityManagerNative grantUriPermissionUncheckedFromIntentLocked(needed, owner); } + @Override public void grantUriPermission(IApplicationThread caller, String targetPkg, Uri uri, int modeFlags) { enforceNotIsolatedCaller("grantUriPermission"); @@ -5474,6 +6063,10 @@ public final class ActivityManagerService extends ActivityManagerNative throw new IllegalArgumentException("null uri"); } + // Persistable only supported through Intents + Preconditions.checkFlagsArgument(modeFlags, + Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + grantUriPermissionLocked(r.uid, targetPkg, uri, modeFlags, null); } @@ -5482,45 +6075,25 @@ public final class ActivityManagerService extends ActivityManagerNative void removeUriPermissionIfNeededLocked(UriPermission perm) { if ((perm.modeFlags&(Intent.FLAG_GRANT_READ_URI_PERMISSION |Intent.FLAG_GRANT_WRITE_URI_PERMISSION)) == 0) { - HashMap<Uri, UriPermission> perms - = mGrantedUriPermissions.get(perm.uid); + ArrayMap<Uri, UriPermission> perms + = mGrantedUriPermissions.get(perm.targetUid); if (perms != null) { if (DEBUG_URI_PERMISSION) Slog.v(TAG, - "Removing " + perm.uid + " permission to " + perm.uri); + "Removing " + perm.targetUid + " permission to " + perm.uri); perms.remove(perm.uri); if (perms.size() == 0) { - mGrantedUriPermissions.remove(perm.uid); + mGrantedUriPermissions.remove(perm.targetUid); } } } } - private void revokeUriPermissionLocked(int callingUid, Uri uri, - int modeFlags) { - modeFlags &= (Intent.FLAG_GRANT_READ_URI_PERMISSION - | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); - if (modeFlags == 0) { - return; - } + private void revokeUriPermissionLocked(int callingUid, Uri uri, int modeFlags) { + if (DEBUG_URI_PERMISSION) Slog.v(TAG, "Revoking all granted permissions to " + uri); - if (DEBUG_URI_PERMISSION) Slog.v(TAG, - "Revoking all granted permissions to " + uri); - final IPackageManager pm = AppGlobals.getPackageManager(); - final String authority = uri.getAuthority(); - ProviderInfo pi = null; - int userId = UserHandle.getUserId(callingUid); - ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, userId); - if (cpr != null) { - pi = cpr.info; - } else { - try { - pi = pm.resolveContentProvider(authority, - PackageManager.GET_URI_PERMISSION_PATTERNS, userId); - } catch (RemoteException ex) { - } - } + final ProviderInfo pi = getProviderInfoLocked(authority, UserHandle.getUserId(callingUid)); if (pi == null) { Slog.w(TAG, "No content provider found for permission revoke: " + uri.toSafeString()); return; @@ -5536,13 +6109,15 @@ public final class ActivityManagerService extends ActivityManagerNative //} } + boolean persistChanged = false; + // Go through all of the permissions and remove any that match. final List<String> SEGMENTS = uri.getPathSegments(); if (SEGMENTS != null) { final int NS = SEGMENTS.size(); int N = mGrantedUriPermissions.size(); for (int i=0; i<N; i++) { - HashMap<Uri, UriPermission> perms + ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.valueAt(i); Iterator<UriPermission> it = perms.values().iterator(); toploop: @@ -5565,8 +6140,8 @@ public final class ActivityManagerService extends ActivityManagerNative } } if (DEBUG_URI_PERMISSION) Slog.v(TAG, - "Revoking " + perm.uid + " permission to " + perm.uri); - perm.clearModes(modeFlags); + "Revoking " + perm.targetUid + " permission to " + perm.uri); + persistChanged |= perm.clearModes(modeFlags, true); if (perm.modeFlags == 0) { it.remove(); } @@ -5579,8 +6154,13 @@ public final class ActivityManagerService extends ActivityManagerNative } } } + + if (persistChanged) { + schedulePersistUriGrants(); + } } + @Override public void revokeUriPermission(IApplicationThread caller, Uri uri, int modeFlags) { enforceNotIsolatedCaller("revokeUriPermission"); @@ -5603,19 +6183,8 @@ public final class ActivityManagerService extends ActivityManagerNative } final IPackageManager pm = AppGlobals.getPackageManager(); - final String authority = uri.getAuthority(); - ProviderInfo pi = null; - ContentProviderRecord cpr = mProviderMap.getProviderByName(authority, r.userId); - if (cpr != null) { - pi = cpr.info; - } else { - try { - pi = pm.resolveContentProvider(authority, - PackageManager.GET_URI_PERMISSION_PATTERNS, r.userId); - } catch (RemoteException ex) { - } - } + final ProviderInfo pi = getProviderInfoLocked(authority, r.userId); if (pi == null) { Slog.w(TAG, "No content provider found for permission revoke: " + uri.toSafeString()); @@ -5626,6 +6195,54 @@ public final class ActivityManagerService extends ActivityManagerNative } } + /** + * Remove any {@link UriPermission} granted <em>from</em> or <em>to</em> the + * given package. + * + * @param packageName Package name to match, or {@code null} to apply to all + * packages. + * @param userHandle User to match, or {@link UserHandle#USER_ALL} to apply + * to all users. + * @param persistable If persistable grants should be removed. + */ + private void removeUriPermissionsForPackageLocked( + String packageName, int userHandle, boolean persistable) { + if (userHandle == UserHandle.USER_ALL && packageName == null) { + throw new IllegalArgumentException("Must narrow by either package or user"); + } + + boolean persistChanged = false; + + final int size = mGrantedUriPermissions.size(); + for (int i = 0; i < size; i++) { + // Only inspect grants matching user + if (userHandle == UserHandle.USER_ALL + || userHandle == UserHandle.getUserId(mGrantedUriPermissions.keyAt(i))) { + final Iterator<UriPermission> it = mGrantedUriPermissions.valueAt(i) + .values().iterator(); + while (it.hasNext()) { + final UriPermission perm = it.next(); + + // Only inspect grants matching package + if (packageName == null || perm.sourcePkg.equals(packageName) + || perm.targetPkg.equals(packageName)) { + persistChanged |= perm.clearModes(~0, persistable); + + // Only remove when no modes remain; any persisted grants + // will keep this alive. + if (perm.modeFlags == 0) { + it.remove(); + } + } + } + } + } + + if (persistChanged) { + schedulePersistUriGrants(); + } + } + @Override public IBinder newUriPermissionOwner(String name) { enforceNotIsolatedCaller("newUriPermissionOwner"); @@ -5677,6 +6294,250 @@ public final class ActivityManagerService extends ActivityManagerNative } } + private void schedulePersistUriGrants() { + if (!mHandler.hasMessages(PERSIST_URI_GRANTS_MSG)) { + mHandler.sendMessageDelayed(mHandler.obtainMessage(PERSIST_URI_GRANTS_MSG), + 10 * DateUtils.SECOND_IN_MILLIS); + } + } + + private void writeGrantedUriPermissions() { + if (DEBUG_URI_PERMISSION) Slog.v(TAG, "writeGrantedUriPermissions()"); + + // Snapshot permissions so we can persist without lock + ArrayList<UriPermission.Snapshot> persist = Lists.newArrayList(); + synchronized (this) { + final int size = mGrantedUriPermissions.size(); + for (int i = 0 ; i < size; i++) { + for (UriPermission perm : mGrantedUriPermissions.valueAt(i).values()) { + if (perm.persistedModeFlags != 0) { + persist.add(perm.snapshot()); + } + } + } + } + + FileOutputStream fos = null; + try { + fos = mGrantFile.startWrite(); + + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, "utf-8"); + out.startDocument(null, true); + out.startTag(null, TAG_URI_GRANTS); + for (UriPermission.Snapshot perm : persist) { + out.startTag(null, TAG_URI_GRANT); + writeIntAttribute(out, ATTR_USER_HANDLE, perm.userHandle); + out.attribute(null, ATTR_SOURCE_PKG, perm.sourcePkg); + out.attribute(null, ATTR_TARGET_PKG, perm.targetPkg); + out.attribute(null, ATTR_URI, String.valueOf(perm.uri)); + writeIntAttribute(out, ATTR_MODE_FLAGS, perm.persistedModeFlags); + writeLongAttribute(out, ATTR_CREATED_TIME, perm.persistedCreateTime); + out.endTag(null, TAG_URI_GRANT); + } + out.endTag(null, TAG_URI_GRANTS); + out.endDocument(); + + mGrantFile.finishWrite(fos); + } catch (IOException e) { + if (fos != null) { + mGrantFile.failWrite(fos); + } + } + } + + private void readGrantedUriPermissionsLocked() { + if (DEBUG_URI_PERMISSION) Slog.v(TAG, "readGrantedUriPermissions()"); + + final long now = System.currentTimeMillis(); + + FileInputStream fis = null; + try { + fis = mGrantFile.openRead(); + final XmlPullParser in = Xml.newPullParser(); + in.setInput(fis, null); + + int type; + while ((type = in.next()) != END_DOCUMENT) { + final String tag = in.getName(); + if (type == START_TAG) { + if (TAG_URI_GRANT.equals(tag)) { + final int userHandle = readIntAttribute(in, ATTR_USER_HANDLE); + final String sourcePkg = in.getAttributeValue(null, ATTR_SOURCE_PKG); + final String targetPkg = in.getAttributeValue(null, ATTR_TARGET_PKG); + final Uri uri = Uri.parse(in.getAttributeValue(null, ATTR_URI)); + final int modeFlags = readIntAttribute(in, ATTR_MODE_FLAGS); + final long createdTime = readLongAttribute(in, ATTR_CREATED_TIME, now); + + // Sanity check that provider still belongs to source package + final ProviderInfo pi = getProviderInfoLocked( + uri.getAuthority(), userHandle); + if (pi != null && sourcePkg.equals(pi.packageName)) { + int targetUid = -1; + try { + targetUid = AppGlobals.getPackageManager() + .getPackageUid(targetPkg, userHandle); + } catch (RemoteException e) { + } + if (targetUid != -1) { + final UriPermission perm = findOrCreateUriPermissionLocked( + sourcePkg, targetPkg, targetUid, uri); + perm.initPersistedModes(modeFlags, createdTime); + } + } else { + Slog.w(TAG, "Persisted grant for " + uri + " had source " + sourcePkg + + " but instead found " + pi); + } + } + } + } + } catch (FileNotFoundException e) { + // Missing grants is okay + } catch (IOException e) { + Log.wtf(TAG, "Failed reading Uri grants", e); + } catch (XmlPullParserException e) { + Log.wtf(TAG, "Failed reading Uri grants", e); + } finally { + IoUtils.closeQuietly(fis); + } + } + + @Override + public void takePersistableUriPermission(Uri uri, int modeFlags) { + enforceNotIsolatedCaller("takePersistableUriPermission"); + + Preconditions.checkFlagsArgument(modeFlags, + Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + synchronized (this) { + final int callingUid = Binder.getCallingUid(); + final UriPermission perm = findUriPermissionLocked(callingUid, uri); + if (perm == null) { + throw new SecurityException("No permission grant found for UID " + callingUid + + " and Uri " + uri.toSafeString()); + } + + boolean persistChanged = perm.takePersistableModes(modeFlags); + persistChanged |= maybePrunePersistedUriGrantsLocked(callingUid); + + if (persistChanged) { + schedulePersistUriGrants(); + } + } + } + + @Override + public void releasePersistableUriPermission(Uri uri, int modeFlags) { + enforceNotIsolatedCaller("releasePersistableUriPermission"); + + Preconditions.checkFlagsArgument(modeFlags, + Intent.FLAG_GRANT_READ_URI_PERMISSION | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); + + synchronized (this) { + final int callingUid = Binder.getCallingUid(); + + final UriPermission perm = findUriPermissionLocked(callingUid, uri); + if (perm == null) { + Slog.w(TAG, "No permission grant found for UID " + callingUid + " and Uri " + + uri.toSafeString()); + return; + } + + final boolean persistChanged = perm.releasePersistableModes(modeFlags); + removeUriPermissionIfNeededLocked(perm); + if (persistChanged) { + schedulePersistUriGrants(); + } + } + } + + /** + * Prune any older {@link UriPermission} for the given UID until outstanding + * persisted grants are below {@link #MAX_PERSISTED_URI_GRANTS}. + * + * @return if any mutations occured that require persisting. + */ + private boolean maybePrunePersistedUriGrantsLocked(int uid) { + final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(uid); + if (perms == null) return false; + if (perms.size() < MAX_PERSISTED_URI_GRANTS) return false; + + final ArrayList<UriPermission> persisted = Lists.newArrayList(); + for (UriPermission perm : perms.values()) { + if (perm.persistedModeFlags != 0) { + persisted.add(perm); + } + } + + final int trimCount = persisted.size() - MAX_PERSISTED_URI_GRANTS; + if (trimCount <= 0) return false; + + Collections.sort(persisted, new UriPermission.PersistedTimeComparator()); + for (int i = 0; i < trimCount; i++) { + final UriPermission perm = persisted.get(i); + + if (DEBUG_URI_PERMISSION) { + Slog.v(TAG, "Trimming grant created at " + perm.persistedCreateTime); + } + + perm.releasePersistableModes(~0); + removeUriPermissionIfNeededLocked(perm); + } + + return true; + } + + @Override + public ParceledListSlice<android.content.UriPermission> getPersistedUriPermissions( + String packageName, boolean incoming) { + enforceNotIsolatedCaller("getPersistedUriPermissions"); + Preconditions.checkNotNull(packageName, "packageName"); + + final int callingUid = Binder.getCallingUid(); + final IPackageManager pm = AppGlobals.getPackageManager(); + try { + final int packageUid = pm.getPackageUid(packageName, UserHandle.getUserId(callingUid)); + if (packageUid != callingUid) { + throw new SecurityException( + "Package " + packageName + " does not belong to calling UID " + callingUid); + } + } catch (RemoteException e) { + throw new SecurityException("Failed to verify package name ownership"); + } + + final ArrayList<android.content.UriPermission> result = Lists.newArrayList(); + synchronized (this) { + if (incoming) { + final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(callingUid); + if (perms == null) { + Slog.w(TAG, "No permission grants found for " + packageName); + } else { + final int size = perms.size(); + for (int i = 0; i < size; i++) { + final UriPermission perm = perms.valueAt(i); + if (packageName.equals(perm.targetPkg) && perm.persistedModeFlags != 0) { + result.add(perm.buildPersistedPublicApiObject()); + } + } + } + } else { + final int size = mGrantedUriPermissions.size(); + for (int i = 0; i < size; i++) { + final ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.valueAt(i); + final int permsSize = perms.size(); + for (int j = 0; j < permsSize; j++) { + final UriPermission perm = perms.valueAt(j); + if (packageName.equals(perm.sourcePkg) && perm.persistedModeFlags != 0) { + result.add(perm.buildPersistedPublicApiObject()); + } + } + } + } + } + return new ParceledListSlice<android.content.UriPermission>(result); + } + + @Override public void showWaitingForDebugger(IApplicationThread who, boolean waiting) { synchronized (this) { ProcessRecord app = @@ -5691,14 +6552,15 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public void getMemoryInfo(ActivityManager.MemoryInfo outInfo) { final long homeAppMem = mProcessList.getMemLevel(ProcessList.HOME_APP_ADJ); - final long hiddenAppMem = mProcessList.getMemLevel(ProcessList.HIDDEN_APP_MIN_ADJ); + final long cachedAppMem = mProcessList.getMemLevel(ProcessList.CACHED_APP_MIN_ADJ); outInfo.availMem = Process.getFreeMemory(); outInfo.totalMem = Process.getTotalMemory(); outInfo.threshold = homeAppMem; - outInfo.lowMemory = outInfo.availMem < (homeAppMem + ((hiddenAppMem-homeAppMem)/2)); - outInfo.hiddenAppThreshold = hiddenAppMem; + outInfo.lowMemory = outInfo.availMem < (homeAppMem + ((cachedAppMem-homeAppMem)/2)); + outInfo.hiddenAppThreshold = cachedAppMem; outInfo.secondaryServerThreshold = mProcessList.getMemLevel( ProcessList.SERVICE_ADJ); outInfo.visibleAppThreshold = mProcessList.getMemLevel( @@ -5711,12 +6573,12 @@ public final class ActivityManagerService extends ActivityManagerNative // TASK MANAGEMENT // ========================================================= - public List getTasks(int maxNum, int flags, + @Override + public List<RunningTaskInfo> getTasks(int maxNum, int flags, IThumbnailReceiver receiver) { - ArrayList list = new ArrayList(); + ArrayList<RunningTaskInfo> list = new ArrayList<RunningTaskInfo>(); - PendingThumbnailsRecord pending = null; - IApplicationThread topThumbnail = null; + PendingThumbnailsRecord pending = new PendingThumbnailsRecord(receiver); ActivityRecord topRecord = null; synchronized(this) { @@ -5742,88 +6604,20 @@ public final class ActivityManagerService extends ActivityManagerNative throw new SecurityException(msg); } - int pos = mMainStack.mHistory.size()-1; - ActivityRecord next = - pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null; - ActivityRecord top = null; - TaskRecord curTask = null; - int numActivities = 0; - int numRunning = 0; - while (pos >= 0 && maxNum > 0) { - final ActivityRecord r = next; - pos--; - next = pos >= 0 ? (ActivityRecord)mMainStack.mHistory.get(pos) : null; - - // Initialize state for next task if needed. - if (top == null || - (top.state == ActivityState.INITIALIZING - && top.task == r.task)) { - top = r; - curTask = r.task; - numActivities = numRunning = 0; - } - - // Add 'r' into the current task. - numActivities++; - if (r.app != null && r.app.thread != null) { - numRunning++; - } - - if (localLOGV) Slog.v( - TAG, r.intent.getComponent().flattenToShortString() - + ": task=" + r.task); - - // If the next one is a different task, generate a new - // TaskInfo entry for what we have. - if (next == null || next.task != curTask) { - ActivityManager.RunningTaskInfo ci - = new ActivityManager.RunningTaskInfo(); - ci.id = curTask.taskId; - ci.baseActivity = r.intent.getComponent(); - ci.topActivity = top.intent.getComponent(); - if (top.thumbHolder != null) { - ci.description = top.thumbHolder.lastDescription; - } - ci.numActivities = numActivities; - ci.numRunning = numRunning; - //System.out.println( - // "#" + maxNum + ": " + " descr=" + ci.description); - if (ci.thumbnail == null && receiver != null) { - if (localLOGV) Slog.v( - TAG, "State=" + top.state + "Idle=" + top.idle - + " app=" + top.app - + " thr=" + (top.app != null ? top.app.thread : null)); - if (top.state == ActivityState.RESUMED - || top.state == ActivityState.PAUSING) { - if (top.idle && top.app != null - && top.app.thread != null) { - topRecord = top; - topThumbnail = top.app.thread; - } else { - top.thumbnailNeeded = true; - } - } - if (pending == null) { - pending = new PendingThumbnailsRecord(receiver); - } - pending.pendingRecords.add(top); - } - list.add(ci); - maxNum--; - top = null; - } - } + // TODO: Improve with MRU list from all ActivityStacks. + topRecord = mStackSupervisor.getTasksLocked(maxNum, receiver, pending, list); - if (pending != null) { + if (!pending.pendingRecords.isEmpty()) { mPendingThumbnails.add(pending); } } if (localLOGV) Slog.v(TAG, "We have pending thumbnails: " + pending); - if (topThumbnail != null) { + if (topRecord != null) { if (localLOGV) Slog.v(TAG, "Requesting top thumbnail"); try { + IApplicationThread topThumbnail = topRecord.app.thread; topThumbnail.requestThumbnail(topRecord.appToken); } catch (Exception e) { Slog.w(TAG, "Exception thrown when requesting thumbnail", e); @@ -5845,6 +6639,11 @@ public final class ActivityManagerService extends ActivityManagerNative return list; } + TaskRecord getMostRecentTask() { + return mRecentTasks.get(0); + } + + @Override public List<ActivityManager.RecentTaskInfo> getRecentTasks(int maxNum, int flags, int userId) { userId = handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, @@ -5889,7 +6688,8 @@ public final class ActivityManagerService extends ActivityManagerNative } rti.origActivity = tr.origActivity; rti.description = tr.lastDescription; - + rti.stackId = tr.stack.mStackId; + if ((flags&ActivityManager.RECENT_IGNORE_UNAVAILABLE) != 0) { // Check whether this activity is currently available. try { @@ -5917,56 +6717,74 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private TaskRecord taskForIdLocked(int id) { + private TaskRecord recentTaskForIdLocked(int id) { final int N = mRecentTasks.size(); - for (int i=0; i<N; i++) { - TaskRecord tr = mRecentTasks.get(i); - if (tr.taskId == id) { - return tr; + for (int i=0; i<N; i++) { + TaskRecord tr = mRecentTasks.get(i); + if (tr.taskId == id) { + return tr; + } } - } - return null; + return null; } + @Override public ActivityManager.TaskThumbnails getTaskThumbnails(int id) { synchronized (this) { enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER, "getTaskThumbnails()"); - TaskRecord tr = taskForIdLocked(id); + TaskRecord tr = recentTaskForIdLocked(id); if (tr != null) { - return mMainStack.getTaskThumbnailsLocked(tr); + return tr.getTaskThumbnailsLocked(); } } return null; } + @Override public Bitmap getTaskTopThumbnail(int id) { synchronized (this) { enforceCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER, "getTaskTopThumbnail()"); - TaskRecord tr = taskForIdLocked(id); + TaskRecord tr = recentTaskForIdLocked(id); if (tr != null) { - return mMainStack.getTaskTopThumbnailLocked(tr); + return tr.getTaskTopThumbnailLocked(); } } return null; } + @Override public boolean removeSubTask(int taskId, int subTaskIndex) { synchronized (this) { enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, "removeSubTask()"); long ident = Binder.clearCallingIdentity(); try { - return mMainStack.removeTaskActivitiesLocked(taskId, subTaskIndex, - true) != null; + TaskRecord tr = recentTaskForIdLocked(taskId); + if (tr != null) { + return tr.removeTaskActivitiesLocked(subTaskIndex, true) != null; + } + return false; } finally { Binder.restoreCallingIdentity(ident); } } } + private void killUnneededProcessLocked(ProcessRecord pr, String reason) { + if (!pr.killedByAm) { + Slog.i(TAG, "Killing " + pr.toShortString() + " (adj " + pr.setAdj + "): " + reason); + EventLog.writeEvent(EventLogTags.AM_KILL, pr.userId, pr.pid, + pr.processName, pr.setAdj, reason); + pr.killedByAm = true; + Process.killProcessQuiet(pr.pid); + } + } + private void cleanUpRemovedTaskLocked(TaskRecord tr, int flags) { + tr.disposeThumbnail(); + mRecentTasks.remove(tr); final boolean killProcesses = (flags&ActivityManager.REMOVE_TASK_KILL_PROCESS) != 0; Intent baseIntent = new Intent( tr.intent != null ? tr.intent : tr.affinityIntent); @@ -5983,14 +6801,15 @@ public final class ActivityManagerService extends ActivityManagerNative // Find any running processes associated with this app. final String pkg = component.getPackageName(); ArrayList<ProcessRecord> procs = new ArrayList<ProcessRecord>(); - HashMap<String, SparseArray<ProcessRecord>> pmap = mProcessNames.getMap(); - for (SparseArray<ProcessRecord> uids : pmap.values()) { - for (int i=0; i<uids.size(); i++) { - ProcessRecord proc = uids.valueAt(i); + ArrayMap<String, SparseArray<ProcessRecord>> pmap = mProcessNames.getMap(); + for (int i=0; i<pmap.size(); i++) { + SparseArray<ProcessRecord> uids = pmap.valueAt(i); + for (int j=0; j<uids.size(); j++) { + ProcessRecord proc = uids.valueAt(j); if (proc.userId != tr.userId) { continue; } - if (!proc.pkgList.contains(pkg)) { + if (!proc.pkgList.containsKey(pkg)) { continue; } procs.add(proc); @@ -6000,12 +6819,12 @@ public final class ActivityManagerService extends ActivityManagerNative // Kill the running processes. for (int i=0; i<procs.size(); i++) { ProcessRecord pr = procs.get(i); + if (pr == mHomeProcess) { + // Don't kill the home process along with tasks from the same package. + continue; + } if (pr.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) { - Slog.i(TAG, "Killing " + pr.toShortString() + ": remove task"); - EventLog.writeEvent(EventLogTags.AM_KILL, pr.userId, pr.pid, - pr.processName, pr.setAdj, "remove task"); - pr.killedBackground = true; - Process.killProcessQuiet(pr.pid); + killUnneededProcessLocked(pr, "remove task"); } else { pr.waitingToKill = "remove task"; } @@ -6013,43 +6832,30 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public boolean removeTask(int taskId, int flags) { synchronized (this) { enforceCallingPermission(android.Manifest.permission.REMOVE_TASKS, "removeTask()"); long ident = Binder.clearCallingIdentity(); try { - ActivityRecord r = mMainStack.removeTaskActivitiesLocked(taskId, -1, - false); - if (r != null) { - mRecentTasks.remove(r.task); - cleanUpRemovedTaskLocked(r.task, flags); - return true; - } else { - TaskRecord tr = null; - int i=0; - while (i < mRecentTasks.size()) { - TaskRecord t = mRecentTasks.get(i); - if (t.taskId == taskId) { - tr = t; - break; - } - i++; + TaskRecord tr = recentTaskForIdLocked(taskId); + if (tr != null) { + ActivityRecord r = tr.removeTaskActivitiesLocked(-1, false); + if (r != null) { + cleanUpRemovedTaskLocked(tr, flags); + return true; } - if (tr != null) { - if (tr.numActivities <= 0) { - // Caller is just removing a recent task that is - // not actively running. That is easy! - mRecentTasks.remove(i); - cleanUpRemovedTaskLocked(tr, flags); - return true; - } else { - Slog.w(TAG, "removeTask: task " + taskId - + " does not have activities to remove, " - + " but numActivities=" + tr.numActivities - + ": " + tr); - } + if (tr.mActivities.size() == 0) { + // Caller is just removing a recent task that is + // not actively running. That is easy! + cleanUpRemovedTaskLocked(tr, flags); + return true; } + Slog.w(TAG, "removeTask: task " + taskId + + " does not have activities to remove, " + + " but numActivities=" + tr.numActivities + + ": " + tr); } } finally { Binder.restoreCallingIdentity(ident); @@ -6058,50 +6864,15 @@ public final class ActivityManagerService extends ActivityManagerNative return false; } - private final int findAffinityTaskTopLocked(int startIndex, String affinity) { - int j; - TaskRecord startTask = ((ActivityRecord)mMainStack.mHistory.get(startIndex)).task; - TaskRecord jt = startTask; - - // First look backwards - for (j=startIndex-1; j>=0; j--) { - ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(j); - if (r.task != jt) { - jt = r.task; - if (affinity.equals(jt.affinity)) { - return j; - } - } - } - - // Now look forwards - final int N = mMainStack.mHistory.size(); - jt = startTask; - for (j=startIndex+1; j<N; j++) { - ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(j); - if (r.task != jt) { - if (affinity.equals(jt.affinity)) { - return j; - } - jt = r.task; - } - } - - // Might it be at the top? - if (affinity.equals(((ActivityRecord)mMainStack.mHistory.get(N-1)).task.affinity)) { - return N-1; - } - - return -1; - } - /** * TODO: Add mController hook */ + @Override public void moveTaskToFront(int task, int flags, Bundle options) { enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, "moveTaskToFront()"); + if (DEBUG_STACK) Slog.d(TAG, "moveTaskToFront: moving task=" + task); synchronized(this) { if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(), Binder.getCallingUid(), "Task to front")) { @@ -6110,34 +6881,7 @@ public final class ActivityManagerService extends ActivityManagerNative } final long origId = Binder.clearCallingIdentity(); try { - TaskRecord tr = taskForIdLocked(task); - if (tr != null) { - if ((flags&ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { - mMainStack.mUserLeaving = true; - } - if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) { - // Caller wants the home activity moved with it. To accomplish this, - // we'll just move the home task to the top first. - mMainStack.moveHomeToFrontLocked(); - } - mMainStack.moveTaskToFrontLocked(tr, null, options); - return; - } - for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { - ActivityRecord hr = (ActivityRecord)mMainStack.mHistory.get(i); - if (hr.task.taskId == task) { - if ((flags&ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { - mMainStack.mUserLeaving = true; - } - if ((flags&ActivityManager.MOVE_TASK_WITH_HOME) != 0) { - // Caller wants the home activity moved with it. To accomplish this, - // we'll just move the home task to the top first. - mMainStack.moveHomeToFrontLocked(); - } - mMainStack.moveTaskToFrontLocked(hr.task, null, options); - return; - } - } + mStackSupervisor.findTaskToMoveToFrontLocked(task, flags, options); } finally { Binder.restoreCallingIdentity(origId); } @@ -6145,21 +6889,29 @@ public final class ActivityManagerService extends ActivityManagerNative } } - public void moveTaskToBack(int task) { + @Override + public void moveTaskToBack(int taskId) { enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, "moveTaskToBack()"); synchronized(this) { - if (mMainStack.mResumedActivity != null - && mMainStack.mResumedActivity.task.taskId == task) { - if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(), - Binder.getCallingUid(), "Task to back")) { - return; + TaskRecord tr = recentTaskForIdLocked(taskId); + if (tr != null) { + if (DEBUG_STACK) Slog.d(TAG, "moveTaskToBack: moving task=" + tr); + ActivityStack stack = tr.stack; + if (stack.mResumedActivity != null && stack.mResumedActivity.task == tr) { + if (!checkAppSwitchAllowedLocked(Binder.getCallingPid(), + Binder.getCallingUid(), "Task to back")) { + return; + } + } + final long origId = Binder.clearCallingIdentity(); + try { + stack.moveTaskToBackLocked(taskId, null); + } finally { + Binder.restoreCallingIdentity(origId); } } - final long origId = Binder.clearCallingIdentity(); - mMainStack.moveTaskToBackLocked(task, null); - Binder.restoreCallingIdentity(origId); } } @@ -6172,19 +6924,21 @@ public final class ActivityManagerService extends ActivityManagerNative * of a task; if true it will work for any activity in a task. * @return Returns true if the move completed, false if not. */ + @Override public boolean moveActivityTaskToBack(IBinder token, boolean nonRoot) { enforceNotIsolatedCaller("moveActivityTaskToBack"); synchronized(this) { final long origId = Binder.clearCallingIdentity(); - int taskId = getTaskForActivityLocked(token, !nonRoot); + int taskId = ActivityRecord.getTaskForActivityLocked(token, !nonRoot); if (taskId >= 0) { - return mMainStack.moveTaskToBackLocked(taskId, null); + return ActivityRecord.getStackLocked(token).moveTaskToBackLocked(taskId, null); } Binder.restoreCallingIdentity(origId); } return false; } + @Override public void moveTaskBackwards(int task) { enforceCallingPermission(android.Manifest.permission.REORDER_TASKS, "moveTaskBackwards()"); @@ -6204,27 +6958,151 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.e(TAG, "moveTaskBackwards not yet implemented!"); } - public int getTaskForActivity(IBinder token, boolean onlyRoot) { - synchronized(this) { - return getTaskForActivityLocked(token, onlyRoot); + @Override + public int createStack(int taskId, int relativeStackBoxId, int position, float weight) { + enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, + "createStack()"); + if (DEBUG_STACK) Slog.d(TAG, "createStack: taskId=" + taskId + " relStackBoxId=" + + relativeStackBoxId + " position=" + position + " weight=" + weight); + synchronized (this) { + long ident = Binder.clearCallingIdentity(); + try { + int stackId = mStackSupervisor.createStack(); + mWindowManager.createStack(stackId, relativeStackBoxId, position, weight); + if (taskId > 0) { + moveTaskToStack(taskId, stackId, true); + } + return stackId; + } finally { + Binder.restoreCallingIdentity(ident); + } } } - int getTaskForActivityLocked(IBinder token, boolean onlyRoot) { - final int N = mMainStack.mHistory.size(); - TaskRecord lastTask = null; - for (int i=0; i<N; i++) { - ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); - if (r.appToken == token) { - if (!onlyRoot || lastTask != r.task) { - return r.task.taskId; + @Override + public void moveTaskToStack(int taskId, int stackId, boolean toTop) { + enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, + "moveTaskToStack()"); + if (stackId == HOME_STACK_ID) { + Slog.e(TAG, "moveTaskToStack: Attempt to move task " + taskId + " to home stack", + new RuntimeException("here").fillInStackTrace()); + } + synchronized (this) { + long ident = Binder.clearCallingIdentity(); + try { + if (DEBUG_STACK) Slog.d(TAG, "moveTaskToStack: moving task=" + taskId + " to stackId=" + + stackId + " toTop=" + toTop); + mStackSupervisor.moveTaskToStack(taskId, stackId, toTop); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + } + + @Override + public void resizeStackBox(int stackBoxId, float weight) { + enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, + "resizeStackBox()"); + long ident = Binder.clearCallingIdentity(); + try { + mWindowManager.resizeStackBox(stackBoxId, weight); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private ArrayList<StackInfo> getStacks() { + synchronized (this) { + ArrayList<ActivityManager.StackInfo> list = new ArrayList<ActivityManager.StackInfo>(); + ArrayList<ActivityStack> stacks = mStackSupervisor.getStacks(); + for (ActivityStack stack : stacks) { + ActivityManager.StackInfo stackInfo = new ActivityManager.StackInfo(); + int stackId = stack.mStackId; + stackInfo.stackId = stackId; + stackInfo.bounds = mWindowManager.getStackBounds(stackId); + ArrayList<TaskRecord> tasks = stack.getAllTasks(); + final int numTasks = tasks.size(); + int[] taskIds = new int[numTasks]; + String[] taskNames = new String[numTasks]; + for (int i = 0; i < numTasks; ++i) { + final TaskRecord task = tasks.get(i); + taskIds[i] = task.taskId; + taskNames[i] = task.origActivity != null ? task.origActivity.flattenToString() + : task.realActivity != null ? task.realActivity.flattenToString() + : task.getTopActivity() != null ? task.getTopActivity().packageName + : "unknown"; + } + stackInfo.taskIds = taskIds; + stackInfo.taskNames = taskNames; + list.add(stackInfo); + } + return list; + } + } + + private void addStackInfoToStackBoxInfo(StackBoxInfo stackBoxInfo, List<StackInfo> stackInfos) { + final int stackId = stackBoxInfo.stackId; + if (stackId >= 0) { + for (StackInfo stackInfo : stackInfos) { + if (stackId == stackInfo.stackId) { + stackBoxInfo.stack = stackInfo; + stackInfos.remove(stackInfo); + return; } - return -1; } - lastTask = r.task; + } else { + addStackInfoToStackBoxInfo(stackBoxInfo.children[0], stackInfos); + addStackInfoToStackBoxInfo(stackBoxInfo.children[1], stackInfos); } + } - return -1; + @Override + public List<StackBoxInfo> getStackBoxes() { + enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, + "getStackBoxes()"); + long ident = Binder.clearCallingIdentity(); + try { + List<StackBoxInfo> stackBoxInfos = mWindowManager.getStackBoxInfos(); + synchronized (this) { + List<StackInfo> stackInfos = getStacks(); + for (StackBoxInfo stackBoxInfo : stackBoxInfos) { + addStackInfoToStackBoxInfo(stackBoxInfo, stackInfos); + } + } + return stackBoxInfos; + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public StackBoxInfo getStackBoxInfo(int stackBoxId) { + enforceCallingPermission(android.Manifest.permission.MANAGE_ACTIVITY_STACKS, + "getStackBoxInfo()"); + long ident = Binder.clearCallingIdentity(); + try { + List<StackBoxInfo> stackBoxInfos = mWindowManager.getStackBoxInfos(); + StackBoxInfo info = null; + synchronized (this) { + List<StackInfo> stackInfos = getStacks(); + for (StackBoxInfo stackBoxInfo : stackBoxInfos) { + addStackInfoToStackBoxInfo(stackBoxInfo, stackInfos); + if (stackBoxInfo.stackBoxId == stackBoxId) { + info = stackBoxInfo; + } + } + } + return info; + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + @Override + public int getTaskForActivity(IBinder token, boolean onlyRoot) { + synchronized(this) { + return ActivityRecord.getTaskForActivityLocked(token, onlyRoot); + } } // ========================================================= @@ -6241,14 +7119,14 @@ public final class ActivityManagerService extends ActivityManagerNative final void sendPendingThumbnail(ActivityRecord r, IBinder token, Bitmap thumbnail, CharSequence description, boolean always) { - TaskRecord task = null; - ArrayList receivers = null; + TaskRecord task; + ArrayList<PendingThumbnailsRecord> receivers = null; //System.out.println("Send pending thumbnail: " + r); synchronized(this) { if (r == null) { - r = mMainStack.isInStackLocked(token); + r = ActivityRecord.isInStackLocked(token); if (r == null) { return; } @@ -6268,12 +7146,11 @@ public final class ActivityManagerService extends ActivityManagerNative int N = mPendingThumbnails.size(); int i=0; while (i<N) { - PendingThumbnailsRecord pr = - (PendingThumbnailsRecord)mPendingThumbnails.get(i); + PendingThumbnailsRecord pr = mPendingThumbnails.get(i); //System.out.println("Looking in " + pr.pendingRecords); if (pr.pendingRecords.remove(r)) { if (receivers == null) { - receivers = new ArrayList(); + receivers = new ArrayList<PendingThumbnailsRecord>(); } receivers.add(pr); if (pr.pendingRecords.size() == 0) { @@ -6291,8 +7168,7 @@ public final class ActivityManagerService extends ActivityManagerNative final int N = receivers.size(); for (int i=0; i<N; i++) { try { - PendingThumbnailsRecord pr = - (PendingThumbnailsRecord)receivers.get(i); + PendingThumbnailsRecord pr = receivers.get(i); pr.receiver.newThumbnail( task != null ? task.taskId : -1, thumbnail, description); if (pr.finished) { @@ -6322,6 +7198,7 @@ public final class ActivityManagerService extends ActivityManagerNative int userId = app.userId; if (providers != null) { int N = providers.size(); + app.pubProviders.ensureCapacity(N + app.pubProviders.size()); for (int i=0; i<N; i++) { ProviderInfo cpi = (ProviderInfo)providers.get(i); @@ -6347,7 +7224,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (DEBUG_MU) Slog.v(TAG_MU, "generateApplicationProvidersLocked, cpi.uid = " + cpr.uid); app.pubProviders.put(cpi.name, cpr); - app.addPackage(cpi.applicationInfo.packageName); + app.addPackage(cpi.applicationInfo.packageName, mProcessStats); ensurePackageDexOpt(cpi.applicationInfo.packageName); } } @@ -6393,7 +7270,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } - HashMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(callingUid); + ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.get(callingUid); if (perms != null) { for (Map.Entry<Uri, UriPermission> uri : perms.entrySet()) { if (uri.getKey().getAuthority().equals(cpi.authority)) { @@ -6531,7 +7408,7 @@ public final class ActivityManagerService extends ActivityManagerNative // make sure to count it as being accessed and thus // back up on the LRU list. This is good because // content providers are often expensive to start. - updateLruProcessLocked(cpr.proc, false); + updateLruProcessLocked(cpr.proc, false, false); } } @@ -6647,7 +7524,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (DEBUG_PROVIDER) { RuntimeException e = new RuntimeException("here"); - Slog.w(TAG, "LAUNCHING REMOTE PROVIDER (myuid " + r.uid + Slog.w(TAG, "LAUNCHING REMOTE PROVIDER (myuid " + (r != null ? r.uid : null) + " pruid " + cpr.appInfo.uid + "): " + cpr.info.name, e); } @@ -6678,16 +7555,30 @@ public final class ActivityManagerService extends ActivityManagerNative + cpr.appInfo.packageName + ": " + e); } - ProcessRecord proc = startProcessLocked(cpi.processName, - cpr.appInfo, false, 0, "content provider", - new ComponentName(cpi.applicationInfo.packageName, - cpi.name), false, false); - if (proc == null) { - Slog.w(TAG, "Unable to launch app " - + cpi.applicationInfo.packageName + "/" - + cpi.applicationInfo.uid + " for provider " - + name + ": process is bad"); - return null; + // Use existing process if already started + ProcessRecord proc = getProcessRecordLocked( + cpi.processName, cpr.appInfo.uid, false); + if (proc != null && proc.thread != null) { + if (DEBUG_PROVIDER) { + Slog.d(TAG, "Installing in existing process " + proc); + } + proc.pubProviders.put(cpi.name, cpr); + try { + proc.thread.scheduleInstallProvider(cpi); + } catch (RemoteException e) { + } + } else { + proc = startProcessLocked(cpi.processName, + cpr.appInfo, false, 0, "content provider", + new ComponentName(cpi.applicationInfo.packageName, + cpi.name), false, false, false); + if (proc == null) { + Slog.w(TAG, "Unable to launch app " + + cpi.applicationInfo.packageName + "/" + + cpi.applicationInfo.uid + " for provider " + + name + ": process is bad"); + return null; + } } cpr.launchingApp = proc; mLaunchingProviders.add(cpr); @@ -6991,6 +7882,31 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override + public void appNotRespondingViaProvider(IBinder connection) { + enforceCallingPermission( + android.Manifest.permission.REMOVE_TASKS, "appNotRespondingViaProvider()"); + + final ContentProviderConnection conn = (ContentProviderConnection) connection; + if (conn == null) { + Slog.w(TAG, "ContentProviderConnection is null"); + return; + } + + final ProcessRecord host = conn.provider.proc; + if (host == null) { + Slog.w(TAG, "Failed to find hosting ProcessRecord"); + return; + } + + final long token = Binder.clearCallingIdentity(); + try { + appNotResponding(host, null, null, false, "ContentProvider not responding"); + } finally { + Binder.restoreCallingIdentity(token); + } + } + public static final void installSystemProviders() { List<ProviderInfo> providers; synchronized (mSelf) { @@ -7055,8 +7971,8 @@ public final class ActivityManagerService extends ActivityManagerNative // GLOBAL MANAGEMENT // ========================================================= - final ProcessRecord newProcessRecordLocked(IApplicationThread thread, - ApplicationInfo info, String customProcess, boolean isolated) { + final ProcessRecord newProcessRecordLocked(ApplicationInfo info, String customProcess, + boolean isolated) { String proc = customProcess != null ? customProcess : info.processName; BatteryStatsImpl.Uid.Proc ps = null; BatteryStatsImpl stats = mBatteryStatsService.getActiveStatistics(); @@ -7064,7 +7980,6 @@ public final class ActivityManagerService extends ActivityManagerNative if (isolated) { int userId = UserHandle.getUserId(uid); int stepsLeft = Process.LAST_ISOLATED_UID - Process.FIRST_ISOLATED_UID + 1; - uid = 0; while (true) { if (mNextIsolatedProcessUid < Process.FIRST_ISOLATED_UID || mNextIsolatedProcessUid > Process.LAST_ISOLATED_UID) { @@ -7085,24 +8000,24 @@ public final class ActivityManagerService extends ActivityManagerNative synchronized (stats) { ps = stats.getProcessStatsLocked(info.uid, proc); } - return new ProcessRecord(ps, thread, info, proc, uid); + return new ProcessRecord(ps, info, proc, uid); } final ProcessRecord addAppLocked(ApplicationInfo info, boolean isolated) { ProcessRecord app; if (!isolated) { - app = getProcessRecordLocked(info.processName, info.uid); + app = getProcessRecordLocked(info.processName, info.uid, true); } else { app = null; } if (app == null) { - app = newProcessRecordLocked(null, info, null, isolated); + app = newProcessRecordLocked(info, null, isolated); mProcessNames.put(info.processName, app.uid, app); if (isolated) { mIsolatedProcesses.put(app.uid, app); } - updateLruProcessLocked(app, true); + updateLruProcessLocked(app, true, false); } // This package really, really can not be stopped. @@ -7133,13 +8048,10 @@ public final class ActivityManagerService extends ActivityManagerNative "unhandledBack()"); synchronized(this) { - int count = mMainStack.mHistory.size(); - if (DEBUG_SWITCH) Slog.d( - TAG, "Performing unhandledBack(): stack size = " + count); - if (count > 1) { - final long origId = Binder.clearCallingIdentity(); - mMainStack.finishActivityLocked((ActivityRecord)mMainStack.mHistory.get(count-1), - count-1, Activity.RESULT_CANCELED, null, "unhandled-back", true); + final long origId = Binder.clearCallingIdentity(); + try { + getFocusedStack().unhandledBackLocked(); + } finally { Binder.restoreCallingIdentity(origId); } } @@ -7162,7 +8074,7 @@ public final class ActivityManagerService extends ActivityManagerNative sCallerIdentity.set(new Identity( Binder.getCallingPid(), Binder.getCallingUid())); try { - pfd = cph.provider.openFile(null, uri, "r"); + pfd = cph.provider.openFile(null, uri, "r", null); } catch (FileNotFoundException e) { // do nothing; pfd will be returned null } finally { @@ -7180,7 +8092,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Actually is sleeping or shutting down or whatever else in the future // is an inactive state. - public boolean isSleeping() { + public boolean isSleepingOrShuttingDown() { return mSleeping || mShuttingDown; } @@ -7197,7 +8109,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (!mSleeping) { mSleeping = true; - mMainStack.stopIfSleepingLocked(); + mStackSupervisor.goingToSleepLocked(); // Initialize the wake times of all processes. checkExcessivePowerUsageLocked(false); @@ -7208,69 +8120,59 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public boolean shutdown(int timeout) { if (checkCallingPermission(android.Manifest.permission.SHUTDOWN) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires permission " + android.Manifest.permission.SHUTDOWN); } - + boolean timedout = false; - + synchronized(this) { mShuttingDown = true; updateEventDispatchingLocked(); - - if (mMainStack.mResumedActivity != null) { - mMainStack.stopIfSleepingLocked(); - final long endTime = System.currentTimeMillis() + timeout; - while (mMainStack.mResumedActivity != null - || mMainStack.mPausingActivity != null) { - long delay = endTime - System.currentTimeMillis(); - if (delay <= 0) { - Slog.w(TAG, "Activity manager shutdown timed out"); - timedout = true; - break; - } - try { - this.wait(); - } catch (InterruptedException e) { - } - } - } + timedout = mStackSupervisor.shutdownLocked(timeout); } mAppOpsService.shutdown(); mUsageStatsService.shutdown(); mBatteryStatsService.shutdown(); - + synchronized (this) { + mProcessStats.shutdownLocked(); + } + return timedout; } public final void activitySlept(IBinder token) { - if (localLOGV) Slog.v( - TAG, "Activity slept: token=" + token); - - ActivityRecord r = null; + if (localLOGV) Slog.v(TAG, "Activity slept: token=" + token); final long origId = Binder.clearCallingIdentity(); synchronized (this) { - r = mMainStack.isInStackLocked(token); + final ActivityRecord r = ActivityRecord.isInStackLocked(token); if (r != null) { - mMainStack.activitySleptLocked(r); + mStackSupervisor.activitySleptLocked(r); } } Binder.restoreCallingIdentity(origId); } + void logLockScreen(String msg) { + if (DEBUG_LOCKSCREEN) Slog.d(TAG, Debug.getCallers(2) + ":" + msg + + " mLockScreenShown=" + mLockScreenShown + " mWentToSleep=" + + mWentToSleep + " mSleeping=" + mSleeping + " mDismissKeyguardOnNextActivity=" + + mStackSupervisor.mDismissKeyguardOnNextActivity); + } + private void comeOutOfSleepIfNeededLocked() { if (!mWentToSleep && !mLockScreenShown) { if (mSleeping) { mSleeping = false; - mMainStack.awakeFromSleepingLocked(); - mMainStack.resumeTopActivityLocked(null); + mStackSupervisor.comeOutOfSleepIfNeededLocked(); } } } @@ -7301,8 +8203,14 @@ public final class ActivityManagerService extends ActivityManagerNative } synchronized(this) { - mLockScreenShown = shown; - comeOutOfSleepIfNeededLocked(); + long ident = Binder.clearCallingIdentity(); + try { + if (DEBUG_LOCKSCREEN) logLockScreen(" shown=" + shown); + mLockScreenShown = shown; + comeOutOfSleepIfNeededLocked(); + } finally { + Binder.restoreCallingIdentity(ident); + } } } @@ -7360,33 +8268,36 @@ public final class ActivityManagerService extends ActivityManagerNative enforceCallingPermission(android.Manifest.permission.SET_DEBUG_APP, "setDebugApp()"); - // Note that this is not really thread safe if there are multiple - // callers into it at the same time, but that's not a situation we - // care about. - if (persistent) { - final ContentResolver resolver = mContext.getContentResolver(); - Settings.Global.putString( - resolver, Settings.Global.DEBUG_APP, - packageName); - Settings.Global.putInt( - resolver, Settings.Global.WAIT_FOR_DEBUGGER, - waitForDebugger ? 1 : 0); - } + long ident = Binder.clearCallingIdentity(); + try { + // Note that this is not really thread safe if there are multiple + // callers into it at the same time, but that's not a situation we + // care about. + if (persistent) { + final ContentResolver resolver = mContext.getContentResolver(); + Settings.Global.putString( + resolver, Settings.Global.DEBUG_APP, + packageName); + Settings.Global.putInt( + resolver, Settings.Global.WAIT_FOR_DEBUGGER, + waitForDebugger ? 1 : 0); + } - synchronized (this) { - if (!persistent) { - mOrigDebugApp = mDebugApp; - mOrigWaitForDebugger = mWaitForDebugger; - } - mDebugApp = packageName; - mWaitForDebugger = waitForDebugger; - mDebugTransient = !persistent; - if (packageName != null) { - final long origId = Binder.clearCallingIdentity(); - forceStopPackageLocked(packageName, -1, false, false, true, true, - UserHandle.USER_ALL); - Binder.restoreCallingIdentity(origId); + synchronized (this) { + if (!persistent) { + mOrigDebugApp = mDebugApp; + mOrigWaitForDebugger = mWaitForDebugger; + } + mDebugApp = packageName; + mWaitForDebugger = waitForDebugger; + mDebugTransient = !persistent; + if (packageName != null) { + forceStopPackageLocked(packageName, -1, false, false, true, true, + UserHandle.USER_ALL, "set debug app"); + } } + } finally { + Binder.restoreCallingIdentity(ident); } } @@ -7427,6 +8338,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public void setAlwaysFinish(boolean enabled) { enforceCallingPermission(android.Manifest.permission.SET_ALWAYS_FINISH, "setAlwaysFinish()"); @@ -7440,6 +8352,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public void setActivityController(IActivityController controller) { enforceCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER, "setActivityController()"); @@ -7449,6 +8362,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public void setUserIsMonkey(boolean userIsMonkey) { synchronized (this) { synchronized (mPidsSelfLocked) { @@ -7466,6 +8380,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public boolean isUserAMonkey() { synchronized (this) { // If there is a controller also implies the user is a monkey. @@ -7489,8 +8404,8 @@ public final class ActivityManagerService extends ActivityManagerNative return KEY_DISPATCHING_TIMEOUT; } - - public long inputDispatchingTimedOut(int pid, final boolean aboveSystem) { + @Override + public long inputDispatchingTimedOut(int pid, final boolean aboveSystem, String reason) { if (checkCallingPermission(android.Manifest.permission.FILTER_EVENTS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires permission " @@ -7505,7 +8420,7 @@ public final class ActivityManagerService extends ActivityManagerNative timeout = getInputDispatchingTimeoutLocked(proc); } - if (!inputDispatchingTimedOut(proc, null, null, aboveSystem)) { + if (!inputDispatchingTimedOut(proc, null, null, aboveSystem, reason)) { return -1; } @@ -7518,13 +8433,20 @@ public final class ActivityManagerService extends ActivityManagerNative */ public boolean inputDispatchingTimedOut(final ProcessRecord proc, final ActivityRecord activity, final ActivityRecord parent, - final boolean aboveSystem) { + final boolean aboveSystem, String reason) { if (checkCallingPermission(android.Manifest.permission.FILTER_EVENTS) != PackageManager.PERMISSION_GRANTED) { throw new SecurityException("Requires permission " + android.Manifest.permission.FILTER_EVENTS); } + final String annotation; + if (reason == null) { + annotation = "Input dispatching timed out"; + } else { + annotation = "Input dispatching timed out (" + reason + ")"; + } + if (proc != null) { synchronized (this) { if (proc.debugging) { @@ -7540,7 +8462,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (proc.instrumentationClass != null) { Bundle info = new Bundle(); info.putString("shortMsg", "keyDispatchingTimedOut"); - info.putString("longMsg", "Timed out while dispatching key event"); + info.putString("longMsg", annotation); finishInstrumentationLocked(proc, Activity.RESULT_CANCELED, info); return true; } @@ -7548,7 +8470,7 @@ public final class ActivityManagerService extends ActivityManagerNative mHandler.post(new Runnable() { @Override public void run() { - appNotResponding(proc, activity, parent, aboveSystem, "keyDispatchingTimedOut"); + appNotResponding(proc, activity, parent, aboveSystem, annotation); } }); } @@ -7556,33 +8478,34 @@ public final class ActivityManagerService extends ActivityManagerNative return true; } - public Bundle getTopActivityExtras(int requestType) { + public Bundle getAssistContextExtras(int requestType) { enforceCallingPermission(android.Manifest.permission.GET_TOP_ACTIVITY_INFO, - "getTopActivityExtras()"); - PendingActivityExtras pae; + "getAssistContextExtras()"); + PendingAssistExtras pae; Bundle extras = new Bundle(); synchronized (this) { - ActivityRecord activity = mMainStack.mResumedActivity; + ActivityRecord activity = getFocusedStack().mResumedActivity; if (activity == null) { - Slog.w(TAG, "getTopActivityExtras failed: no resumed activity"); + Slog.w(TAG, "getAssistContextExtras failed: no resumed activity"); return null; } extras.putString(Intent.EXTRA_ASSIST_PACKAGE, activity.packageName); if (activity.app == null || activity.app.thread == null) { - Slog.w(TAG, "getTopActivityExtras failed: no process for " + activity); + Slog.w(TAG, "getAssistContextExtras failed: no process for " + activity); return extras; } if (activity.app.pid == Binder.getCallingPid()) { - Slog.w(TAG, "getTopActivityExtras failed: request process same as " + activity); + Slog.w(TAG, "getAssistContextExtras failed: request process same as " + activity); return extras; } - pae = new PendingActivityExtras(activity); + pae = new PendingAssistExtras(activity); try { - activity.app.thread.requestActivityExtras(activity.appToken, pae, requestType); - mPendingActivityExtras.add(pae); - mHandler.postDelayed(pae, PENDING_ACTIVITY_RESULT_TIMEOUT); + activity.app.thread.requestAssistContextExtras(activity.appToken, pae, + requestType); + mPendingAssistExtras.add(pae); + mHandler.postDelayed(pae, PENDING_ASSIST_EXTRAS_TIMEOUT); } catch (RemoteException e) { - Slog.w(TAG, "getTopActivityExtras failed: crash calling " + activity); + Slog.w(TAG, "getAssistContextExtras failed: crash calling " + activity); return extras; } } @@ -7598,14 +8521,14 @@ public final class ActivityManagerService extends ActivityManagerNative } } synchronized (this) { - mPendingActivityExtras.remove(pae); + mPendingAssistExtras.remove(pae); mHandler.removeCallbacks(pae); } return extras; } - public void reportTopActivityExtras(IBinder token, Bundle extras) { - PendingActivityExtras pae = (PendingActivityExtras)token; + public void reportAssistContextExtras(IBinder token, Bundle extras) { + PendingAssistExtras pae = (PendingAssistExtras)token; synchronized (pae) { pae.result = extras; pae.haveResult = true; @@ -7621,15 +8544,60 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public void unregisterProcessObserver(IProcessObserver observer) { synchronized (this) { mProcessObservers.unregister(observer); } } + @Override + public boolean convertFromTranslucent(IBinder token) { + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (this) { + final ActivityRecord r = ActivityRecord.isInStackLocked(token); + if (r == null) { + return false; + } + if (r.changeWindowTranslucency(true)) { + mWindowManager.setAppFullscreen(token, true); + mStackSupervisor.ensureActivitiesVisibleLocked(null, 0); + return true; + } + return false; + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + @Override + public boolean convertToTranslucent(IBinder token) { + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (this) { + final ActivityRecord r = ActivityRecord.isInStackLocked(token); + if (r == null) { + return false; + } + if (r.changeWindowTranslucency(false)) { + r.task.stack.convertToTranslucent(r); + mWindowManager.setAppFullscreen(token, false); + mStackSupervisor.ensureActivitiesVisibleLocked(null, 0); + return true; + } + return false; + } + } finally { + Binder.restoreCallingIdentity(origId); + } + } + + @Override public void setImmersive(IBinder token, boolean immersive) { synchronized(this) { - final ActivityRecord r = mMainStack.isInStackLocked(token); + final ActivityRecord r = ActivityRecord.isInStackLocked(token); if (r == null) { throw new IllegalArgumentException(); } @@ -7645,9 +8613,10 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override public boolean isImmersive(IBinder token) { synchronized (this) { - ActivityRecord r = mMainStack.isInStackLocked(token); + ActivityRecord r = ActivityRecord.isInStackLocked(token); if (r == null) { throw new IllegalArgumentException(); } @@ -7658,7 +8627,7 @@ public final class ActivityManagerService extends ActivityManagerNative public boolean isTopActivityImmersive() { enforceNotIsolatedCaller("startActivity"); synchronized (this) { - ActivityRecord r = mMainStack.topRunningActivityLocked(null); + ActivityRecord r = getFocusedStack().topRunningActivityLocked(null); return (r != null) ? r.immersive : false; } } @@ -7717,7 +8686,7 @@ public final class ActivityManagerService extends ActivityManagerNative String reason = (pReason == null) ? "Unknown" : pReason; // XXX Note: don't acquire main activity lock here, because the window // manager calls in with its locks held. - + boolean killed = false; synchronized (mPidsSelfLocked) { int[] types = new int[pids.length]; @@ -7732,12 +8701,12 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - - // If the worst oom_adj is somewhere in the hidden proc LRU range, - // then constrain it so we will kill all hidden procs. - if (worstType < ProcessList.HIDDEN_APP_MAX_ADJ - && worstType > ProcessList.HIDDEN_APP_MIN_ADJ) { - worstType = ProcessList.HIDDEN_APP_MIN_ADJ; + + // If the worst oom_adj is somewhere in the cached proc LRU range, + // then constrain it so we will kill all cached procs. + if (worstType < ProcessList.CACHED_APP_MAX_ADJ + && worstType > ProcessList.CACHED_APP_MIN_ADJ) { + worstType = ProcessList.CACHED_APP_MIN_ADJ; } // If this is not a secure call, don't let it kill processes that @@ -7753,13 +8722,9 @@ public final class ActivityManagerService extends ActivityManagerNative continue; } int adj = proc.setAdj; - if (adj >= worstType && !proc.killedBackground) { - Slog.w(TAG, "Killing " + proc + " (adj " + adj + "): " + reason); - EventLog.writeEvent(EventLogTags.AM_KILL, proc.userId, proc.pid, - proc.processName, adj, reason); + if (adj >= worstType && !proc.killedByAm) { + killUnneededProcessLocked(proc, reason); killed = true; - proc.killedBackground = true; - Process.killProcessQuiet(pids[i]); } } } @@ -7801,13 +8766,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (proc == null) continue; final int adj = proc.setAdj; - if (adj > belowAdj && !proc.killedBackground) { - Slog.w(TAG, "Killing " + proc + " (adj " + adj + "): " + reason); - EventLog.writeEvent(EventLogTags.AM_KILL, proc.userId, - proc.pid, proc.processName, adj, reason); + if (adj > belowAdj && !proc.killedByAm) { + killUnneededProcessLocked(proc, reason); killed = true; - proc.killedBackground = true; - Process.killProcessQuiet(pid); } } } @@ -7853,6 +8814,95 @@ public final class ActivityManagerService extends ActivityManagerNative } } + @Override + public void restart() { + if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires permission " + + android.Manifest.permission.SET_ACTIVITY_WATCHER); + } + + Log.i(TAG, "Sending shutdown broadcast..."); + + BroadcastReceiver br = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { + // Now the broadcast is done, finish up the low-level shutdown. + Log.i(TAG, "Shutting down activity manager..."); + shutdown(10000); + Log.i(TAG, "Shutdown complete, restarting!"); + Process.killProcess(Process.myPid()); + System.exit(10); + } + }; + + // First send the high-level shut down broadcast. + Intent intent = new Intent(Intent.ACTION_SHUTDOWN); + intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND); + intent.putExtra(Intent.EXTRA_SHUTDOWN_USERSPACE_ONLY, true); + /* For now we are not doing a clean shutdown, because things seem to get unhappy. + mContext.sendOrderedBroadcastAsUser(intent, + UserHandle.ALL, null, br, mHandler, 0, null, null); + */ + br.onReceive(mContext, intent); + } + + private long getLowRamTimeSinceIdle(long now) { + return mLowRamTimeSinceLastIdle + (mLowRamStartTime > 0 ? (now-mLowRamStartTime) : 0); + } + + @Override + public void performIdleMaintenance() { + if (checkCallingPermission(android.Manifest.permission.SET_ACTIVITY_WATCHER) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires permission " + + android.Manifest.permission.SET_ACTIVITY_WATCHER); + } + + synchronized (this) { + final long now = SystemClock.uptimeMillis(); + final long timeSinceLastIdle = now - mLastIdleTime; + final long lowRamSinceLastIdle = getLowRamTimeSinceIdle(now); + mLastIdleTime = now; + mLowRamTimeSinceLastIdle = 0; + if (mLowRamStartTime != 0) { + mLowRamStartTime = now; + } + + StringBuilder sb = new StringBuilder(128); + sb.append("Idle maintenance over "); + TimeUtils.formatDuration(timeSinceLastIdle, sb); + sb.append(" low RAM for "); + TimeUtils.formatDuration(lowRamSinceLastIdle, sb); + Slog.i(TAG, sb.toString()); + + // If at least 1/3 of our time since the last idle period has been spent + // with RAM low, then we want to kill processes. + boolean doKilling = lowRamSinceLastIdle > (timeSinceLastIdle/3); + + for (int i = mLruProcesses.size() - 1 ; i >= 0 ; i--) { + ProcessRecord proc = mLruProcesses.get(i); + if (proc.notCachedSinceIdle) { + if (proc.setProcState > ActivityManager.PROCESS_STATE_TOP + && proc.setProcState <= ActivityManager.PROCESS_STATE_SERVICE) { + if (doKilling && proc.initialIdlePss != 0 + && proc.lastPss > ((proc.initialIdlePss*3)/2)) { + killUnneededProcessLocked(proc, "idle maint (pss " + proc.lastPss + + " from " + proc.initialIdlePss + ")"); + } + } + } else if (proc.setProcState < ActivityManager.PROCESS_STATE_HOME) { + proc.notCachedSinceIdle = true; + proc.initialIdlePss = 0; + proc.nextPssTime = ProcessList.computeNextPssTime(proc.curProcState, true, + mSleeping, now); + } + } + + mHandler.removeMessages(REQUEST_ALL_PSS_MSG); + mHandler.sendEmptyMessageDelayed(REQUEST_ALL_PSS_MSG, 2*60*1000); + } + } + public final void startRunning(String pkg, String cls, String action, String data) { synchronized(this) { @@ -7880,9 +8930,17 @@ public final class ActivityManagerService extends ActivityManagerNative resolver, Settings.Global.WAIT_FOR_DEBUGGER, 0) != 0; boolean alwaysFinishActivities = Settings.Global.getInt( resolver, Settings.Global.ALWAYS_FINISH_ACTIVITIES, 0) != 0; + boolean forceRtl = Settings.Global.getInt( + resolver, Settings.Global.DEVELOPMENT_FORCE_RTL, 0) != 0; + // Transfer any global setting for forcing RTL layout, into a System Property + SystemProperties.set(Settings.Global.DEVELOPMENT_FORCE_RTL, forceRtl ? "1":"0"); Configuration configuration = new Configuration(); Settings.System.getConfiguration(resolver, configuration); + if (forceRtl) { + // This will take care of setting the correct layout direction flags + configuration.setLayoutDirection(configuration.locale); + } synchronized (this) { mDebugApp = mOrigDebugApp = debugApp; @@ -7899,7 +8957,7 @@ public final class ActivityManagerService extends ActivityManagerNative // no need to synchronize(this) just to read & return the value return mSystemReady; } - + private static File getCalledPreBootReceiversFile() { File dataDir = Environment.getDataDirectory(); File systemDir = new File(dataDir, "system"); @@ -8146,6 +9204,10 @@ public final class ActivityManagerService extends ActivityManagerNative retrieveSettings(); + synchronized (this) { + readGrantedUriPermissionsLocked(); + } + if (goingCallback != null) goingCallback.run(); synchronized (this) { @@ -8207,7 +9269,7 @@ public final class ActivityManagerService extends ActivityManagerNative } finally { Binder.restoreCallingIdentity(ident); } - mMainStack.resumeTopActivityLocked(null); + mStackSupervisor.resumeTopActivitiesLocked(); sendUserSwitchBroadcastsLocked(-1, mCurrentUserId); } } @@ -8275,10 +9337,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (app.pid > 0 && app.pid != MY_PID) { handleAppCrashLocked(app); - Slog.i(ActivityManagerService.TAG, "Killing " + app + ": user's request"); - EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid, - app.processName, app.setAdj, "user's request after error"); - Process.killProcessQuiet(app.pid); + killUnneededProcessLocked(app, "user request after error"); } } } @@ -8302,15 +9361,7 @@ public final class ActivityManagerService extends ActivityManagerNative + " has crashed too many times: killing!"); EventLog.writeEvent(EventLogTags.AM_PROCESS_CRASHED_TOO_MUCH, app.userId, app.info.processName, app.uid); - for (int i=mMainStack.mHistory.size()-1; i>=0; i--) { - ActivityRecord r = (ActivityRecord)mMainStack.mHistory.get(i); - if (r.app == app) { - Slog.w(TAG, " Force finishing activity " - + r.intent.getComponent().flattenToShortString()); - r.stack.finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "crashed", false); - } - } + mStackSupervisor.handleAppCrashLocked(app); if (!app.persistent) { // We don't want to start this process again until the user // explicitly does so... but for persistent process, we really @@ -8330,61 +9381,32 @@ public final class ActivityManagerService extends ActivityManagerNative // annoy the user repeatedly. Unless it is persistent, since those // processes run critical code. removeProcessLocked(app, false, false, "crash"); - mMainStack.resumeTopActivityLocked(null); + mStackSupervisor.resumeTopActivitiesLocked(); return false; } - mMainStack.resumeTopActivityLocked(null); + mStackSupervisor.resumeTopActivitiesLocked(); } else { - ActivityRecord r = mMainStack.topRunningActivityLocked(null); - if (r != null && r.app == app) { - // If the top running activity is from this crashing - // process, then terminate it to avoid getting in a loop. - Slog.w(TAG, " Force finishing activity " - + r.intent.getComponent().flattenToShortString()); - int index = mMainStack.indexOfActivityLocked(r); - r.stack.finishActivityLocked(r, index, - Activity.RESULT_CANCELED, null, "crashed", false); - // Also terminate any activities below it that aren't yet - // stopped, to avoid a situation where one will get - // re-start our crashing activity once it gets resumed again. - index--; - if (index >= 0) { - r = (ActivityRecord)mMainStack.mHistory.get(index); - if (r.state == ActivityState.RESUMED - || r.state == ActivityState.PAUSING - || r.state == ActivityState.PAUSED) { - if (!r.isHomeActivity || mHomeProcess != r.app) { - Slog.w(TAG, " Force finishing activity " - + r.intent.getComponent().flattenToShortString()); - r.stack.finishActivityLocked(r, index, - Activity.RESULT_CANCELED, null, "crashed", false); - } - } - } - } + mStackSupervisor.finishTopRunningActivityLocked(app); } // Bump up the crash count of any services currently running in the proc. - if (app.services.size() != 0) { + for (int i=app.services.size()-1; i>=0; i--) { // Any services running in the application need to be placed // back in the pending list. - Iterator<ServiceRecord> it = app.services.iterator(); - while (it.hasNext()) { - ServiceRecord sr = it.next(); - sr.crashCount++; - } + ServiceRecord sr = app.services.valueAt(i); + sr.crashCount++; } // If the crashing process is what we consider to be the "home process" and it has been // replaced by a third-party app, clear the package preferred activities from packages // with a home activity running in the process to prevent a repeatedly crashing app // from blocking the user to manually clear the list. - if (app == mHomeProcess && mHomeProcess.activities.size() > 0 + final ArrayList<ActivityRecord> activities = app.activities; + if (app == mHomeProcess && activities.size() > 0 && (mHomeProcess.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { - Iterator it = mHomeProcess.activities.iterator(); - while (it.hasNext()) { - ActivityRecord r = (ActivityRecord)it.next(); - if (r.isHomeActivity) { + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if (r.isHomeActivity()) { Log.i(TAG, "Clearing package preferred activities from " + r.packageName); try { ActivityThread.getPackageManager() @@ -8670,7 +9692,9 @@ public final class ActivityManagerService extends ActivityManagerNative } synchronized (this) { - for (SparseArray<ProcessRecord> apps : mProcessNames.getMap().values()) { + final int NP = mProcessNames.getMap().size(); + for (int ip=0; ip<NP; ip++) { + SparseArray<ProcessRecord> apps = mProcessNames.getMap().valueAt(ip); final int NA = apps.size(); for (int ia=0; ia<NA; ia++) { ProcessRecord p = apps.valueAt(ia); @@ -8711,7 +9735,8 @@ public final class ActivityManagerService extends ActivityManagerNative int flags = process.info.flags; IPackageManager pm = AppGlobals.getPackageManager(); sb.append("Flags: 0x").append(Integer.toString(flags, 16)).append("\n"); - for (String pkg : process.pkgList) { + for (int ip=0; ip<process.pkgList.size(); ip++) { + String pkg = process.pkgList.keyAt(ip); sb.append("Package: ").append(pkg); try { PackageInfo pi = pm.getPackageInfo(pkg, 0, UserHandle.getCallingUserId()); @@ -9029,9 +10054,9 @@ public final class ActivityManagerService extends ActivityManagerNative } static int oomAdjToImportance(int adj, ActivityManager.RunningAppProcessInfo currApp) { - if (adj >= ProcessList.HIDDEN_APP_MIN_ADJ) { + if (adj >= ProcessList.CACHED_APP_MIN_ADJ) { if (currApp != null) { - currApp.lru = adj - ProcessList.HIDDEN_APP_MIN_ADJ + 1; + currApp.lru = adj - ProcessList.CACHED_APP_MIN_ADJ + 1; } return ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND; } else if (adj >= ProcessList.SERVICE_B_ADJ) { @@ -9344,39 +10369,28 @@ public final class ActivityManagerService extends ActivityManagerNative // No piece of data specified, dump everything. synchronized (this) { - boolean needSep; - needSep = dumpPendingIntentsLocked(fd, pw, args, opti, dumpAll, dumpPackage); - if (needSep) { - pw.println(" "); - } + dumpPendingIntentsLocked(fd, pw, args, opti, dumpAll, dumpPackage); + pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); } - needSep = dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage); - if (needSep) { - pw.println(" "); - } + dumpBroadcastsLocked(fd, pw, args, opti, dumpAll, dumpPackage); + pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); } - needSep = dumpProvidersLocked(fd, pw, args, opti, dumpAll, dumpPackage); - if (needSep) { - pw.println(" "); - } + dumpProvidersLocked(fd, pw, args, opti, dumpAll, dumpPackage); + pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); } - needSep = mServices.dumpServicesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage); - if (needSep) { - pw.println(" "); - } + mServices.dumpServicesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage); + pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); } - needSep = dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage); - if (needSep) { - pw.println(" "); - } + dumpActivitiesLocked(fd, pw, args, opti, dumpAll, dumpClient, dumpPackage); + pw.println(); if (dumpAll) { pw.println("-------------------------------------------------------------------------------"); } @@ -9385,57 +10399,32 @@ public final class ActivityManagerService extends ActivityManagerNative Binder.restoreCallingIdentity(origId); } - boolean dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args, + void dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, boolean dumpClient, String dumpPackage) { pw.println("ACTIVITY MANAGER ACTIVITIES (dumpsys activity activities)"); - pw.println(" Main stack:"); - dumpHistoryList(fd, pw, mMainStack.mHistory, " ", "Hist", true, !dumpAll, dumpClient, - dumpPackage); - pw.println(" "); - pw.println(" Running activities (most recent first):"); - dumpHistoryList(fd, pw, mMainStack.mLRUActivities, " ", "Run", false, !dumpAll, false, + + boolean printedAnything = mStackSupervisor.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage); - if (mMainStack.mWaitingVisibleActivities.size() > 0) { - pw.println(" "); - pw.println(" Activities waiting for another to become visible:"); - dumpHistoryList(fd, pw, mMainStack.mWaitingVisibleActivities, " ", "Wait", false, - !dumpAll, false, dumpPackage); - } - if (mMainStack.mStoppingActivities.size() > 0) { - pw.println(" "); - pw.println(" Activities waiting to stop:"); - dumpHistoryList(fd, pw, mMainStack.mStoppingActivities, " ", "Stop", false, - !dumpAll, false, dumpPackage); - } - if (mMainStack.mGoingToSleepActivities.size() > 0) { - pw.println(" "); - pw.println(" Activities waiting to sleep:"); - dumpHistoryList(fd, pw, mMainStack.mGoingToSleepActivities, " ", "Sleep", false, - !dumpAll, false, dumpPackage); - } - if (mMainStack.mFinishingActivities.size() > 0) { - pw.println(" "); - pw.println(" Activities waiting to finish:"); - dumpHistoryList(fd, pw, mMainStack.mFinishingActivities, " ", "Fin", false, - !dumpAll, false, dumpPackage); - } - - pw.println(" "); - if (mMainStack.mPausingActivity != null) { - pw.println(" mPausingActivity: " + mMainStack.mPausingActivity); - } - pw.println(" mResumedActivity: " + mMainStack.mResumedActivity); - pw.println(" mFocusedActivity: " + mFocusedActivity); - if (dumpAll) { - pw.println(" mLastPausedActivity: " + mMainStack.mLastPausedActivity); - pw.println(" mSleepTimeout: " + mMainStack.mSleepTimeout); - pw.println(" mDismissKeyguardOnNextActivity: " - + mMainStack.mDismissKeyguardOnNextActivity); + boolean needSep = printedAnything; + + boolean printed = ActivityStackSupervisor.printThisActivity(pw, mFocusedActivity, + dumpPackage, needSep, " mFocusedActivity: "); + if (printed) { + printedAnything = true; + needSep = false; + } + + if (dumpPackage == null) { + if (needSep) { + pw.println(); + } + needSep = true; + printedAnything = true; + mStackSupervisor.dump(pw, " "); } if (mRecentTasks.size() > 0) { - pw.println(); - pw.println(" Recent tasks:"); + boolean printedHeader = false; final int N = mRecentTasks.size(); for (int i=0; i<N; i++) { @@ -9446,6 +10435,14 @@ public final class ActivityManagerService extends ActivityManagerNative continue; } } + if (!printedHeader) { + if (needSep) { + pw.println(); + } + pw.println(" Recent tasks:"); + printedHeader = true; + printedAnything = true; + } pw.print(" * Recent #"); pw.print(i); pw.print(": "); pw.println(tr); if (dumpAll) { @@ -9453,33 +10450,34 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - - if (dumpAll) { - pw.println(" "); - pw.println(" mCurTask: " + mCurTask); + + if (!printedAnything) { + pw.println(" (nothing)"); } - - return true; } - boolean dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args, + void dumpProcessesLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage) { boolean needSep = false; + boolean printedAnything = false; int numPers = 0; pw.println("ACTIVITY MANAGER RUNNING PROCESSES (dumpsys activity processes)"); if (dumpAll) { - for (SparseArray<ProcessRecord> procs : mProcessNames.getMap().values()) { + final int NP = mProcessNames.getMap().size(); + for (int ip=0; ip<NP; ip++) { + SparseArray<ProcessRecord> procs = mProcessNames.getMap().valueAt(ip); final int NA = procs.size(); for (int ia=0; ia<NA; ia++) { ProcessRecord r = procs.valueAt(ia); - if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) { + if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) { continue; } if (!needSep) { pw.println(" All known processes:"); needSep = true; + printedAnything = true; } pw.print(r.persistent ? " *PERS*" : " *APP*"); pw.print(" UID "); pw.print(procs.keyAt(ia)); @@ -9493,41 +10491,55 @@ public final class ActivityManagerService extends ActivityManagerNative } if (mIsolatedProcesses.size() > 0) { - if (needSep) pw.println(" "); - needSep = true; - pw.println(" Isolated process list (sorted by uid):"); + boolean printed = false; for (int i=0; i<mIsolatedProcesses.size(); i++) { ProcessRecord r = mIsolatedProcesses.valueAt(i); - if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) { + if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) { continue; } + if (!printed) { + if (needSep) { + pw.println(); + } + pw.println(" Isolated process list (sorted by uid):"); + printedAnything = true; + printed = true; + needSep = true; + } pw.println(String.format("%sIsolated #%2d: %s", " ", i, r.toString())); } } if (mLruProcesses.size() > 0) { - if (needSep) pw.println(" "); - needSep = true; - pw.println(" Process LRU list (sorted by oom_adj):"); - dumpProcessOomList(pw, this, mLruProcesses, " ", - "Proc", "PERS", false, dumpPackage); + if (needSep) { + pw.println(); + } + pw.print(" Process LRU list (sorted by oom_adj, "); pw.print(mLruProcesses.size()); + pw.print(" total, non-act at "); + pw.print(mLruProcesses.size()-mLruProcessActivityStart); + pw.print(", non-svc at "); + pw.print(mLruProcesses.size()-mLruProcessServiceStart); + pw.println("):"); + dumpProcessOomList(pw, this, mLruProcesses, " ", "Proc", "PERS", false, dumpPackage); needSep = true; + printedAnything = true; } - if (dumpAll) { + if (dumpAll || dumpPackage != null) { synchronized (mPidsSelfLocked) { boolean printed = false; for (int i=0; i<mPidsSelfLocked.size(); i++) { ProcessRecord r = mPidsSelfLocked.valueAt(i); - if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) { + if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) { continue; } if (!printed) { - if (needSep) pw.println(" "); + if (needSep) pw.println(); needSep = true; pw.println(" PID mappings:"); printed = true; + printedAnything = true; } pw.print(" PID #"); pw.print(mPidsSelfLocked.keyAt(i)); pw.print(": "); pw.println(mPidsSelfLocked.valueAt(i)); @@ -9542,14 +10554,15 @@ public final class ActivityManagerService extends ActivityManagerNative ProcessRecord r = mPidsSelfLocked.get( mForegroundProcesses.valueAt(i).pid); if (dumpPackage != null && (r == null - || !dumpPackage.equals(r.info.packageName))) { + || !r.pkgList.containsKey(dumpPackage))) { continue; } if (!printed) { - if (needSep) pw.println(" "); + if (needSep) pw.println(); needSep = true; pw.println(" Foreground Processes:"); printed = true; + printedAnything = true; } pw.print(" PID #"); pw.print(mForegroundProcesses.keyAt(i)); pw.print(": "); pw.println(mForegroundProcesses.valueAt(i)); @@ -9558,24 +10571,27 @@ public final class ActivityManagerService extends ActivityManagerNative } if (mPersistentStartingProcesses.size() > 0) { - if (needSep) pw.println(" "); + if (needSep) pw.println(); needSep = true; + printedAnything = true; pw.println(" Persisent processes that are starting:"); dumpProcessList(pw, this, mPersistentStartingProcesses, " ", "Starting Norm", "Restarting PERS", dumpPackage); } if (mRemovedProcesses.size() > 0) { - if (needSep) pw.println(" "); + if (needSep) pw.println(); needSep = true; + printedAnything = true; pw.println(" Processes that are being removed:"); dumpProcessList(pw, this, mRemovedProcesses, " ", "Removed Norm", "Removed PERS", dumpPackage); } if (mProcessesOnHold.size() > 0) { - if (needSep) pw.println(" "); + if (needSep) pw.println(); needSep = true; + printedAnything = true; pw.println(" Processes that are on old until the system is ready:"); dumpProcessList(pw, this, mProcessesOnHold, " ", "OnHold Norm", "OnHold PERS", dumpPackage); @@ -9586,23 +10602,25 @@ public final class ActivityManagerService extends ActivityManagerNative if (mProcessCrashTimes.getMap().size() > 0) { boolean printed = false; long now = SystemClock.uptimeMillis(); - for (Map.Entry<String, SparseArray<Long>> procs - : mProcessCrashTimes.getMap().entrySet()) { - String pname = procs.getKey(); - SparseArray<Long> uids = procs.getValue(); + final ArrayMap<String, SparseArray<Long>> pmap = mProcessCrashTimes.getMap(); + final int NP = pmap.size(); + for (int ip=0; ip<NP; ip++) { + String pname = pmap.keyAt(ip); + SparseArray<Long> uids = pmap.valueAt(ip); final int N = uids.size(); for (int i=0; i<N; i++) { int puid = uids.keyAt(i); ProcessRecord r = mProcessNames.get(pname, puid); if (dumpPackage != null && (r == null - || !dumpPackage.equals(r.info.packageName))) { + || !r.pkgList.containsKey(dumpPackage))) { continue; } if (!printed) { - if (needSep) pw.println(" "); + if (needSep) pw.println(); needSep = true; pw.println(" Time since processes crashed:"); printed = true; + printedAnything = true; } pw.print(" Process "); pw.print(pname); pw.print(" uid "); pw.print(puid); @@ -9615,22 +10633,24 @@ public final class ActivityManagerService extends ActivityManagerNative if (mBadProcesses.getMap().size() > 0) { boolean printed = false; - for (Map.Entry<String, SparseArray<Long>> procs - : mBadProcesses.getMap().entrySet()) { - String pname = procs.getKey(); - SparseArray<Long> uids = procs.getValue(); + final ArrayMap<String, SparseArray<Long>> pmap = mBadProcesses.getMap(); + final int NP = pmap.size(); + for (int ip=0; ip<NP; ip++) { + String pname = pmap.keyAt(ip); + SparseArray<Long> uids = pmap.valueAt(ip); final int N = uids.size(); for (int i=0; i<N; i++) { int puid = uids.keyAt(i); ProcessRecord r = mProcessNames.get(pname, puid); if (dumpPackage != null && (r == null - || !dumpPackage.equals(r.info.packageName))) { + || !r.pkgList.containsKey(dumpPackage))) { continue; } if (!printed) { - if (needSep) pw.println(" "); + if (needSep) pw.println(); needSep = true; pw.println(" Bad processes:"); + printedAnything = true; } pw.print(" Bad process "); pw.print(pname); pw.print(" uid "); pw.print(puid); @@ -9640,42 +10660,66 @@ public final class ActivityManagerService extends ActivityManagerNative } } - pw.println(); - pw.println(" mStartedUsers:"); - for (int i=0; i<mStartedUsers.size(); i++) { - UserStartedState uss = mStartedUsers.valueAt(i); - pw.print(" User #"); pw.print(uss.mHandle.getIdentifier()); - pw.print(": "); uss.dump("", pw); - } - pw.print(" mStartedUserArray: ["); - for (int i=0; i<mStartedUserArray.length; i++) { - if (i > 0) pw.print(", "); - pw.print(mStartedUserArray[i]); + if (dumpPackage == null) { + pw.println(); + needSep = false; + pw.println(" mStartedUsers:"); + for (int i=0; i<mStartedUsers.size(); i++) { + UserStartedState uss = mStartedUsers.valueAt(i); + pw.print(" User #"); pw.print(uss.mHandle.getIdentifier()); + pw.print(": "); uss.dump("", pw); + } + pw.print(" mStartedUserArray: ["); + for (int i=0; i<mStartedUserArray.length; i++) { + if (i > 0) pw.print(", "); + pw.print(mStartedUserArray[i]); + } + pw.println("]"); + pw.print(" mUserLru: ["); + for (int i=0; i<mUserLru.size(); i++) { + if (i > 0) pw.print(", "); + pw.print(mUserLru.get(i)); + } + pw.println("]"); + if (dumpAll) { + pw.print(" mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray)); + } } - pw.println("]"); - pw.print(" mUserLru: ["); - for (int i=0; i<mUserLru.size(); i++) { - if (i > 0) pw.print(", "); - pw.print(mUserLru.get(i)); + if (mHomeProcess != null && (dumpPackage == null + || mHomeProcess.pkgList.containsKey(dumpPackage))) { + if (needSep) { + pw.println(); + needSep = false; + } + pw.println(" mHomeProcess: " + mHomeProcess); } - pw.println("]"); - if (dumpAll) { - pw.print(" mStartedUserArray: "); pw.println(Arrays.toString(mStartedUserArray)); + if (mPreviousProcess != null && (dumpPackage == null + || mPreviousProcess.pkgList.containsKey(dumpPackage))) { + if (needSep) { + pw.println(); + needSep = false; + } + pw.println(" mPreviousProcess: " + mPreviousProcess); } - pw.println(" mHomeProcess: " + mHomeProcess); - pw.println(" mPreviousProcess: " + mPreviousProcess); if (dumpAll) { StringBuilder sb = new StringBuilder(128); sb.append(" mPreviousProcessVisibleTime: "); TimeUtils.formatDuration(mPreviousProcessVisibleTime, sb); pw.println(sb); } - if (mHeavyWeightProcess != null) { + if (mHeavyWeightProcess != null && (dumpPackage == null + || mHeavyWeightProcess.pkgList.containsKey(dumpPackage))) { + if (needSep) { + pw.println(); + needSep = false; + } pw.println(" mHeavyWeightProcess: " + mHeavyWeightProcess); } - pw.println(" mConfiguration: " + mConfiguration); + if (dumpPackage == null) { + pw.println(" mConfiguration: " + mConfiguration); + } if (dumpAll) { - pw.println(" mConfigWillChange: " + mMainStack.mConfigWillChange); + pw.println(" mConfigWillChange: " + getFocusedStack().mConfigWillChange); if (mCompatModePackages.getPackages().size() > 0) { boolean printed = false; for (Map.Entry<String, Integer> entry @@ -9694,57 +10738,92 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - if (mSleeping || mWentToSleep || mLockScreenShown) { - pw.println(" mSleeping=" + mSleeping + " mWentToSleep=" + mWentToSleep - + " mLockScreenShown " + mLockScreenShown); - } - if (mShuttingDown) { - pw.println(" mShuttingDown=" + mShuttingDown); + if (dumpPackage == null) { + if (mSleeping || mWentToSleep || mLockScreenShown) { + pw.println(" mSleeping=" + mSleeping + " mWentToSleep=" + mWentToSleep + + " mLockScreenShown " + mLockScreenShown); + } + if (mShuttingDown) { + pw.println(" mShuttingDown=" + mShuttingDown); + } } if (mDebugApp != null || mOrigDebugApp != null || mDebugTransient || mOrigWaitForDebugger) { - pw.println(" mDebugApp=" + mDebugApp + "/orig=" + mOrigDebugApp - + " mDebugTransient=" + mDebugTransient - + " mOrigWaitForDebugger=" + mOrigWaitForDebugger); + if (dumpPackage == null || dumpPackage.equals(mDebugApp) + || dumpPackage.equals(mOrigDebugApp)) { + if (needSep) { + pw.println(); + needSep = false; + } + pw.println(" mDebugApp=" + mDebugApp + "/orig=" + mOrigDebugApp + + " mDebugTransient=" + mDebugTransient + + " mOrigWaitForDebugger=" + mOrigWaitForDebugger); + } } if (mOpenGlTraceApp != null) { - pw.println(" mOpenGlTraceApp=" + mOpenGlTraceApp); + if (dumpPackage == null || dumpPackage.equals(mOpenGlTraceApp)) { + if (needSep) { + pw.println(); + needSep = false; + } + pw.println(" mOpenGlTraceApp=" + mOpenGlTraceApp); + } } if (mProfileApp != null || mProfileProc != null || mProfileFile != null || mProfileFd != null) { - pw.println(" mProfileApp=" + mProfileApp + " mProfileProc=" + mProfileProc); - pw.println(" mProfileFile=" + mProfileFile + " mProfileFd=" + mProfileFd); - pw.println(" mProfileType=" + mProfileType + " mAutoStopProfiler=" - + mAutoStopProfiler); + if (dumpPackage == null || dumpPackage.equals(mProfileApp)) { + if (needSep) { + pw.println(); + needSep = false; + } + pw.println(" mProfileApp=" + mProfileApp + " mProfileProc=" + mProfileProc); + pw.println(" mProfileFile=" + mProfileFile + " mProfileFd=" + mProfileFd); + pw.println(" mProfileType=" + mProfileType + " mAutoStopProfiler=" + + mAutoStopProfiler); + } } - if (mAlwaysFinishActivities || mController != null) { - pw.println(" mAlwaysFinishActivities=" + mAlwaysFinishActivities - + " mController=" + mController); + if (dumpPackage == null) { + if (mAlwaysFinishActivities || mController != null) { + pw.println(" mAlwaysFinishActivities=" + mAlwaysFinishActivities + + " mController=" + mController); + } + if (dumpAll) { + pw.println(" Total persistent processes: " + numPers); + pw.println(" mStartRunning=" + mStartRunning + + " mProcessesReady=" + mProcessesReady + + " mSystemReady=" + mSystemReady); + pw.println(" mBooting=" + mBooting + + " mBooted=" + mBooted + + " mFactoryTest=" + mFactoryTest); + pw.print(" mLastPowerCheckRealtime="); + TimeUtils.formatDuration(mLastPowerCheckRealtime, pw); + pw.println(""); + pw.print(" mLastPowerCheckUptime="); + TimeUtils.formatDuration(mLastPowerCheckUptime, pw); + pw.println(""); + pw.println(" mGoingToSleep=" + mStackSupervisor.mGoingToSleep); + pw.println(" mLaunchingActivity=" + mStackSupervisor.mLaunchingActivity); + pw.println(" mAdjSeq=" + mAdjSeq + " mLruSeq=" + mLruSeq); + pw.println(" mNumNonCachedProcs=" + mNumNonCachedProcs + + " (" + mLruProcesses.size() + " total)" + + " mNumCachedHiddenProcs=" + mNumCachedHiddenProcs + + " mNumServiceProcs=" + mNumServiceProcs + + " mNewNumServiceProcs=" + mNewNumServiceProcs); + pw.println(" mAllowLowerMemLevel=" + mAllowLowerMemLevel + + " mLastMemoryLevel" + mLastMemoryLevel + + " mLastNumProcesses" + mLastNumProcesses); + long now = SystemClock.uptimeMillis(); + pw.print(" mLastIdleTime="); + TimeUtils.formatDuration(now, mLastIdleTime, pw); + pw.print(" mLowRamSinceLastIdle="); + TimeUtils.formatDuration(getLowRamTimeSinceIdle(now), pw); + pw.println(); + } } - if (dumpAll) { - pw.println(" Total persistent processes: " + numPers); - pw.println(" mStartRunning=" + mStartRunning - + " mProcessesReady=" + mProcessesReady - + " mSystemReady=" + mSystemReady); - pw.println(" mBooting=" + mBooting - + " mBooted=" + mBooted - + " mFactoryTest=" + mFactoryTest); - pw.print(" mLastPowerCheckRealtime="); - TimeUtils.formatDuration(mLastPowerCheckRealtime, pw); - pw.println(""); - pw.print(" mLastPowerCheckUptime="); - TimeUtils.formatDuration(mLastPowerCheckUptime, pw); - pw.println(""); - pw.println(" mGoingToSleep=" + mMainStack.mGoingToSleep); - pw.println(" mLaunchingActivity=" + mMainStack.mLaunchingActivity); - pw.println(" mAdjSeq=" + mAdjSeq + " mLruSeq=" + mLruSeq); - pw.println(" mNumNonHiddenProcs=" + mNumNonHiddenProcs - + " mNumHiddenProcs=" + mNumHiddenProcs - + " mNumServiceProcs=" + mNumServiceProcs - + " mNewNumServiceProcs=" + mNewNumServiceProcs); + + if (!printedAnything) { + pw.println(" (nothing)"); } - - return true; } boolean dumpProcessesToGc(FileDescriptor fd, PrintWriter pw, String[] args, @@ -9758,7 +10837,7 @@ public final class ActivityManagerService extends ActivityManagerNative continue; } if (!printed) { - if (needSep) pw.println(" "); + if (needSep) pw.println(); needSep = true; pw.println(" Processes that are waiting to GC:"); printed = true; @@ -9776,37 +10855,56 @@ public final class ActivityManagerService extends ActivityManagerNative return needSep; } + void printOomLevel(PrintWriter pw, String name, int adj) { + pw.print(" "); + if (adj >= 0) { + pw.print(' '); + if (adj < 10) pw.print(' '); + } else { + if (adj > -10) pw.print(' '); + } + pw.print(adj); + pw.print(": "); + pw.print(name); + pw.print(" ("); + pw.print(mProcessList.getMemLevel(adj)/1024); + pw.println(" kB)"); + } + boolean dumpOomLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll) { boolean needSep = false; if (mLruProcesses.size() > 0) { - if (needSep) pw.println(" "); + if (needSep) pw.println(); needSep = true; pw.println(" OOM levels:"); - pw.print(" SYSTEM_ADJ: "); pw.println(ProcessList.SYSTEM_ADJ); - pw.print(" PERSISTENT_PROC_ADJ: "); pw.println(ProcessList.PERSISTENT_PROC_ADJ); - pw.print(" FOREGROUND_APP_ADJ: "); pw.println(ProcessList.FOREGROUND_APP_ADJ); - pw.print(" VISIBLE_APP_ADJ: "); pw.println(ProcessList.VISIBLE_APP_ADJ); - pw.print(" PERCEPTIBLE_APP_ADJ: "); pw.println(ProcessList.PERCEPTIBLE_APP_ADJ); - pw.print(" HEAVY_WEIGHT_APP_ADJ: "); pw.println(ProcessList.HEAVY_WEIGHT_APP_ADJ); - pw.print(" BACKUP_APP_ADJ: "); pw.println(ProcessList.BACKUP_APP_ADJ); - pw.print(" SERVICE_ADJ: "); pw.println(ProcessList.SERVICE_ADJ); - pw.print(" HOME_APP_ADJ: "); pw.println(ProcessList.HOME_APP_ADJ); - pw.print(" PREVIOUS_APP_ADJ: "); pw.println(ProcessList.PREVIOUS_APP_ADJ); - pw.print(" SERVICE_B_ADJ: "); pw.println(ProcessList.SERVICE_B_ADJ); - pw.print(" HIDDEN_APP_MIN_ADJ: "); pw.println(ProcessList.HIDDEN_APP_MIN_ADJ); - pw.print(" HIDDEN_APP_MAX_ADJ: "); pw.println(ProcessList.HIDDEN_APP_MAX_ADJ); - - if (needSep) pw.println(" "); - needSep = true; - pw.println(" Process OOM control:"); - dumpProcessOomList(pw, this, mLruProcesses, " ", - "Proc", "PERS", true, null); + printOomLevel(pw, "SYSTEM_ADJ", ProcessList.SYSTEM_ADJ); + printOomLevel(pw, "PERSISTENT_PROC_ADJ", ProcessList.PERSISTENT_PROC_ADJ); + printOomLevel(pw, "FOREGROUND_APP_ADJ", ProcessList.FOREGROUND_APP_ADJ); + printOomLevel(pw, "VISIBLE_APP_ADJ", ProcessList.VISIBLE_APP_ADJ); + printOomLevel(pw, "PERCEPTIBLE_APP_ADJ", ProcessList.PERCEPTIBLE_APP_ADJ); + printOomLevel(pw, "BACKUP_APP_ADJ", ProcessList.BACKUP_APP_ADJ); + printOomLevel(pw, "HEAVY_WEIGHT_APP_ADJ", ProcessList.HEAVY_WEIGHT_APP_ADJ); + printOomLevel(pw, "SERVICE_ADJ", ProcessList.SERVICE_ADJ); + printOomLevel(pw, "HOME_APP_ADJ", ProcessList.HOME_APP_ADJ); + printOomLevel(pw, "PREVIOUS_APP_ADJ", ProcessList.PREVIOUS_APP_ADJ); + printOomLevel(pw, "SERVICE_B_ADJ", ProcessList.SERVICE_B_ADJ); + printOomLevel(pw, "CACHED_APP_MIN_ADJ", ProcessList.CACHED_APP_MIN_ADJ); + printOomLevel(pw, "CACHED_APP_MAX_ADJ", ProcessList.CACHED_APP_MAX_ADJ); + + if (needSep) pw.println(); + pw.print(" Process OOM control ("); pw.print(mLruProcesses.size()); + pw.print(" total, non-act at "); + pw.print(mLruProcesses.size()-mLruProcessActivityStart); + pw.print(", non-svc at "); + pw.print(mLruProcesses.size()-mLruProcessServiceStart); + pw.println("):"); + dumpProcessOomList(pw, this, mLruProcesses, " ", "Proc", "PERS", true, null); needSep = true; } - needSep = dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, null); + dumpProcessesToGc(fd, pw, args, opti, needSep, dumpAll, null); pw.println(); pw.println(" mHomeProcess: " + mHomeProcess); @@ -9920,32 +11018,10 @@ public final class ActivityManagerService extends ActivityManagerNative */ protected boolean dumpActivity(FileDescriptor fd, PrintWriter pw, String name, String[] args, int opti, boolean dumpAll) { - ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>(); - - if ("all".equals(name)) { - synchronized (this) { - for (ActivityRecord r1 : (ArrayList<ActivityRecord>)mMainStack.mHistory) { - activities.add(r1); - } - } - } else if ("top".equals(name)) { - synchronized (this) { - final int N = mMainStack.mHistory.size(); - if (N > 0) { - activities.add((ActivityRecord)mMainStack.mHistory.get(N-1)); - } - } - } else { - ItemMatcher matcher = new ItemMatcher(); - matcher.build(name); - - synchronized (this) { - for (ActivityRecord r1 : (ArrayList<ActivityRecord>)mMainStack.mHistory) { - if (matcher.match(r1, r1.intent.getComponent())) { - activities.add(r1); - } - } - } + ArrayList<ActivityRecord> activities; + + synchronized (this) { + activities = mStackSupervisor.getDumpActivitiesLocked(name); } if (activities.size() <= 0) { @@ -9953,12 +11029,12 @@ public final class ActivityManagerService extends ActivityManagerNative } String[] newArgs = new String[args.length - opti]; - if (args.length > 2) System.arraycopy(args, opti, newArgs, 0, args.length - opti); + System.arraycopy(args, opti, newArgs, 0, args.length - opti); TaskRecord lastTask = null; boolean needSep = false; for (int i=activities.size()-1; i>=0; i--) { - ActivityRecord r = (ActivityRecord)activities.get(i); + ActivityRecord r = activities.get(i); if (needSep) { pw.println(); } @@ -10016,10 +11092,11 @@ public final class ActivityManagerService extends ActivityManagerNative } } - boolean dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args, + void dumpBroadcastsLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage) { boolean needSep = false; boolean onlyHistory = false; + boolean printedAnything = false; if ("history".equals(dumpPackage)) { if (opti < args.length && "-s".equals(args[opti])) { @@ -10044,6 +11121,7 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" Registered Receivers:"); needSep = true; printed = true; + printedAnything = true; } pw.print(" * "); pw.println(r); r.dump(pw, " "); @@ -10054,11 +11132,13 @@ public final class ActivityManagerService extends ActivityManagerNative "\n Receiver Resolver Table:" : " Receiver Resolver Table:", " ", dumpPackage, false)) { needSep = true; + printedAnything = true; } } for (BroadcastQueue q : mBroadcastQueues) { needSep = q.dumpLocked(fd, pw, args, opti, dumpAll, dumpPackage, needSep); + printedAnything |= needSep; } needSep = true; @@ -10069,6 +11149,7 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(); } needSep = true; + printedAnything = true; pw.print(" Sticky broadcasts for user "); pw.print(mStickyBroadcasts.keyAt(user)); pw.println(":"); StringBuilder sb = new StringBuilder(128); @@ -10106,21 +11187,26 @@ public final class ActivityManagerService extends ActivityManagerNative pw.println(" mHandler:"); mHandler.dump(new PrintWriterPrinter(pw), " "); needSep = true; + printedAnything = true; } - return needSep; + if (!printedAnything) { + pw.println(" (nothing)"); + } } - boolean dumpProvidersLocked(FileDescriptor fd, PrintWriter pw, String[] args, + void dumpProvidersLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage) { - boolean needSep = true; + boolean needSep; + boolean printedAnything = false; ItemMatcher matcher = new ItemMatcher(); matcher.build(args, opti); pw.println("ACTIVITY MANAGER CONTENT PROVIDERS (dumpsys activity providers)"); - mProviderMap.dumpProvidersLocked(pw, dumpAll); + needSep = mProviderMap.dumpProvidersLocked(pw, dumpAll, dumpPackage); + printedAnything |= needSep; if (mLaunchingProviders.size() > 0) { boolean printed = false; @@ -10130,10 +11216,11 @@ public final class ActivityManagerService extends ActivityManagerNative continue; } if (!printed) { - if (needSep) pw.println(" "); + if (needSep) pw.println(); needSep = true; pw.println(" Launching content providers:"); printed = true; + printedAnything = true; } pw.print(" Launching #"); pw.print(i); pw.print(": "); pw.println(r); @@ -10141,13 +11228,29 @@ public final class ActivityManagerService extends ActivityManagerNative } if (mGrantedUriPermissions.size() > 0) { - if (needSep) pw.println(); - needSep = true; - pw.println("Granted Uri Permissions:"); + boolean printed = false; + int dumpUid = -2; + if (dumpPackage != null) { + try { + dumpUid = mContext.getPackageManager().getPackageUid(dumpPackage, 0); + } catch (NameNotFoundException e) { + dumpUid = -1; + } + } for (int i=0; i<mGrantedUriPermissions.size(); i++) { int uid = mGrantedUriPermissions.keyAt(i); - HashMap<Uri, UriPermission> perms + if (dumpUid >= -1 && UserHandle.getAppId(uid) != dumpUid) { + continue; + } + ArrayMap<Uri, UriPermission> perms = mGrantedUriPermissions.valueAt(i); + if (!printed) { + if (needSep) pw.println(); + needSep = true; + pw.println(" Granted Uri Permissions:"); + printed = true; + printedAnything = true; + } pw.print(" * UID "); pw.print(uid); pw.println(" holds:"); for (UriPermission perm : perms.values()) { @@ -10157,18 +11260,20 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - needSep = true; } - - return needSep; + + if (!printedAnything) { + pw.println(" (nothing)"); + } } - boolean dumpPendingIntentsLocked(FileDescriptor fd, PrintWriter pw, String[] args, + void dumpPendingIntentsLocked(FileDescriptor fd, PrintWriter pw, String[] args, int opti, boolean dumpAll, String dumpPackage) { - boolean needSep = false; - + boolean printed = false; + + pw.println("ACTIVITY MANAGER PENDING INTENTS (dumpsys activity intents)"); + if (mIntentSenderRecords.size() > 0) { - boolean printed = false; Iterator<WeakReference<PendingIntentRecord>> it = mIntentSenderRecords.values().iterator(); while (it.hasNext()) { @@ -10178,11 +11283,7 @@ public final class ActivityManagerService extends ActivityManagerNative || !dumpPackage.equals(rec.key.packageName))) { continue; } - if (!printed) { - pw.println("ACTIVITY MANAGER PENDING INTENTS (dumpsys activity intents)"); - printed = true; - } - needSep = true; + printed = true; if (rec != null) { pw.print(" * "); pw.println(rec); if (dumpAll) { @@ -10193,87 +11294,12 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - - return needSep; - } - private static final void dumpHistoryList(FileDescriptor fd, PrintWriter pw, List list, - String prefix, String label, boolean complete, boolean brief, boolean client, - String dumpPackage) { - TaskRecord lastTask = null; - boolean needNL = false; - final String innerPrefix = prefix + " "; - final String[] args = new String[0]; - for (int i=list.size()-1; i>=0; i--) { - final ActivityRecord r = (ActivityRecord)list.get(i); - if (dumpPackage != null && !dumpPackage.equals(r.packageName)) { - continue; - } - final boolean full = !brief && (complete || !r.isInHistory()); - if (needNL) { - pw.println(" "); - needNL = false; - } - if (lastTask != r.task) { - lastTask = r.task; - pw.print(prefix); - pw.print(full ? "* " : " "); - pw.println(lastTask); - if (full) { - lastTask.dump(pw, prefix + " "); - } else if (complete) { - // Complete + brief == give a summary. Isn't that obvious?!? - if (lastTask.intent != null) { - pw.print(prefix); pw.print(" "); - pw.println(lastTask.intent.toInsecureStringWithClip()); - } - } - } - pw.print(prefix); pw.print(full ? " * " : " "); pw.print(label); - pw.print(" #"); pw.print(i); pw.print(": "); - pw.println(r); - if (full) { - r.dump(pw, innerPrefix); - } else if (complete) { - // Complete + brief == give a summary. Isn't that obvious?!? - pw.print(innerPrefix); pw.println(r.intent.toInsecureString()); - if (r.app != null) { - pw.print(innerPrefix); pw.println(r.app); - } - } - if (client && r.app != null && r.app.thread != null) { - // flush anything that is already in the PrintWriter since the thread is going - // to write to the file descriptor directly - pw.flush(); - try { - TransferPipe tp = new TransferPipe(); - try { - r.app.thread.dumpActivity(tp.getWriteFd().getFileDescriptor(), - r.appToken, innerPrefix, args); - // Short timeout, since blocking here can - // deadlock with the application. - tp.go(fd, 2000); - } finally { - tp.kill(); - } - } catch (IOException e) { - pw.println(innerPrefix + "Failure while dumping the activity: " + e); - } catch (RemoteException e) { - pw.println(innerPrefix + "Got a RemoteException while dumping the activity"); - } - needNL = true; - } + if (!printed) { + pw.println(" (nothing)"); } } - private static String buildOomTag(String prefix, String space, int val, int base) { - if (val == base) { - if (space == null) return prefix; - return prefix + " "; - } - return prefix + "+" + Integer.toString(val-base); - } - private static final int dumpProcessList(PrintWriter pw, ActivityManagerService service, List list, String prefix, String normalLabel, String persistentLabel, @@ -10304,7 +11330,7 @@ public final class ActivityManagerService extends ActivityManagerNative = new ArrayList<Pair<ProcessRecord, Integer>>(origList.size()); for (int i=0; i<origList.size(); i++) { ProcessRecord r = origList.get(i); - if (dumpPackage != null && !dumpPackage.equals(r.info.packageName)) { + if (dumpPackage != null && !r.pkgList.containsKey(dumpPackage)) { continue; } list.add(new Pair<ProcessRecord, Integer>(origList.get(i), i)); @@ -10313,7 +11339,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (list.size() <= 0) { return false; } - + Comparator<Pair<ProcessRecord, Integer>> comparator = new Comparator<Pair<ProcessRecord, Integer>>() { @Override @@ -10338,58 +11364,50 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=list.size()-1; i>=0; i--) { ProcessRecord r = list.get(i).first; - String oomAdj; - if (r.setAdj >= ProcessList.HIDDEN_APP_MIN_ADJ) { - oomAdj = buildOomTag("bak", " ", r.setAdj, ProcessList.HIDDEN_APP_MIN_ADJ); - } else if (r.setAdj >= ProcessList.SERVICE_B_ADJ) { - oomAdj = buildOomTag("svcb ", null, r.setAdj, ProcessList.SERVICE_B_ADJ); - } else if (r.setAdj >= ProcessList.PREVIOUS_APP_ADJ) { - oomAdj = buildOomTag("prev ", null, r.setAdj, ProcessList.PREVIOUS_APP_ADJ); - } else if (r.setAdj >= ProcessList.HOME_APP_ADJ) { - oomAdj = buildOomTag("home ", null, r.setAdj, ProcessList.HOME_APP_ADJ); - } else if (r.setAdj >= ProcessList.SERVICE_ADJ) { - oomAdj = buildOomTag("svc ", null, r.setAdj, ProcessList.SERVICE_ADJ); - } else if (r.setAdj >= ProcessList.BACKUP_APP_ADJ) { - oomAdj = buildOomTag("bkup ", null, r.setAdj, ProcessList.BACKUP_APP_ADJ); - } else if (r.setAdj >= ProcessList.HEAVY_WEIGHT_APP_ADJ) { - oomAdj = buildOomTag("hvy ", null, r.setAdj, ProcessList.HEAVY_WEIGHT_APP_ADJ); - } else if (r.setAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) { - oomAdj = buildOomTag("prcp ", null, r.setAdj, ProcessList.PERCEPTIBLE_APP_ADJ); - } else if (r.setAdj >= ProcessList.VISIBLE_APP_ADJ) { - oomAdj = buildOomTag("vis ", null, r.setAdj, ProcessList.VISIBLE_APP_ADJ); - } else if (r.setAdj >= ProcessList.FOREGROUND_APP_ADJ) { - oomAdj = buildOomTag("fore ", null, r.setAdj, ProcessList.FOREGROUND_APP_ADJ); - } else if (r.setAdj >= ProcessList.PERSISTENT_PROC_ADJ) { - oomAdj = buildOomTag("pers ", null, r.setAdj, ProcessList.PERSISTENT_PROC_ADJ); - } else if (r.setAdj >= ProcessList.SYSTEM_ADJ) { - oomAdj = buildOomTag("sys ", null, r.setAdj, ProcessList.SYSTEM_ADJ); - } else { - oomAdj = Integer.toString(r.setAdj); - } - String schedGroup; + String oomAdj = ProcessList.makeOomAdjString(r.setAdj); + char schedGroup; switch (r.setSchedGroup) { case Process.THREAD_GROUP_BG_NONINTERACTIVE: - schedGroup = "B"; + schedGroup = 'B'; break; case Process.THREAD_GROUP_DEFAULT: - schedGroup = "F"; + schedGroup = 'F'; break; default: - schedGroup = Integer.toString(r.setSchedGroup); + schedGroup = '?'; break; } - String foreground; + char foreground; if (r.foregroundActivities) { - foreground = "A"; + foreground = 'A'; } else if (r.foregroundServices) { - foreground = "S"; + foreground = 'S'; } else { - foreground = " "; - } - pw.println(String.format("%s%s #%2d: adj=%s/%s%s trm=%2d %s (%s)", - prefix, (r.persistent ? persistentLabel : normalLabel), - (origList.size()-1)-list.get(i).second, oomAdj, schedGroup, - foreground, r.trimMemoryLevel, r.toShortString(), r.adjType)); + foreground = ' '; + } + String procState = ProcessList.makeProcStateString(r.curProcState); + pw.print(prefix); + pw.print(r.persistent ? persistentLabel : normalLabel); + pw.print(" #"); + int num = (origList.size()-1)-list.get(i).second; + if (num < 10) pw.print(' '); + pw.print(num); + pw.print(": "); + pw.print(oomAdj); + pw.print(' '); + pw.print(schedGroup); + pw.print('/'); + pw.print(foreground); + pw.print('/'); + pw.print(procState); + pw.print(" trm:"); + if (r.trimMemoryLevel < 10) pw.print(' '); + pw.print(r.trimMemoryLevel); + pw.print(' '); + pw.print(r.toShortString()); + pw.print(" ("); + pw.print(r.adjType); + pw.println(')'); if (r.adjSource != null || r.adjTarget != null) { pw.print(prefix); pw.print(" "); @@ -10415,17 +11433,20 @@ public final class ActivityManagerService extends ActivityManagerNative pw.print(prefix); pw.print(" "); pw.print("oom: max="); pw.print(r.maxAdj); - pw.print(" hidden="); pw.print(r.hiddenAdj); - pw.print(" client="); pw.print(r.clientHiddenAdj); - pw.print(" empty="); pw.print(r.emptyAdj); pw.print(" curRaw="); pw.print(r.curRawAdj); pw.print(" setRaw="); pw.print(r.setRawAdj); pw.print(" cur="); pw.print(r.curAdj); pw.print(" set="); pw.println(r.setAdj); pw.print(prefix); pw.print(" "); + pw.print("state: cur="); pw.print(ProcessList.makeProcStateString(r.curProcState)); + pw.print(" set="); pw.print(ProcessList.makeProcStateString(r.setProcState)); + pw.print(" lastPss="); pw.print(r.lastPss); + pw.print(" lastCachedPss="); pw.println(r.lastCachedPss); + pw.print(prefix); + pw.print(" "); pw.print("keeping="); pw.print(r.keeping); - pw.print(" hidden="); pw.print(r.hidden); + pw.print(" cached="); pw.print(r.cached); pw.print(" empty="); pw.print(r.empty); pw.print(" hasAboveClient="); pw.println(r.hasAboveClient); @@ -10566,23 +11587,37 @@ public final class ActivityManagerService extends ActivityManagerNative } final static class MemItem { + final boolean isProc; final String label; final String shortLabel; final long pss; final int id; + final boolean hasActivities; ArrayList<MemItem> subitems; + public MemItem(String _label, String _shortLabel, long _pss, int _id, + boolean _hasActivities) { + isProc = true; + label = _label; + shortLabel = _shortLabel; + pss = _pss; + id = _id; + hasActivities = _hasActivities; + } + public MemItem(String _label, String _shortLabel, long _pss, int _id) { + isProc = false; label = _label; shortLabel = _shortLabel; pss = _pss; id = _id; + hasActivities = false; } } - static final void dumpMemItems(PrintWriter pw, String prefix, ArrayList<MemItem> items, - boolean sort) { - if (sort) { + static final void dumpMemItems(PrintWriter pw, String prefix, String tag, + ArrayList<MemItem> items, boolean sort, boolean isCompact) { + if (sort && !isCompact) { Collections.sort(items, new Comparator<MemItem>() { @Override public int compare(MemItem lhs, MemItem rhs) { @@ -10598,9 +11633,19 @@ public final class ActivityManagerService extends ActivityManagerNative for (int i=0; i<items.size(); i++) { MemItem mi = items.get(i); - pw.print(prefix); pw.printf("%7d kB: ", mi.pss); pw.println(mi.label); + if (!isCompact) { + pw.print(prefix); pw.printf("%7d kB: ", mi.pss); pw.println(mi.label); + } else if (mi.isProc) { + pw.print("proc,"); pw.print(tag); pw.print(","); pw.print(mi.shortLabel); + pw.print(","); pw.print(mi.id); pw.print(","); pw.print(mi.pss); + pw.println(mi.hasActivities ? ",a" : ",e"); + } else { + pw.print(tag); pw.print(","); pw.print(mi.shortLabel); pw.print(","); + pw.println(mi.pss); + } if (mi.subitems != null) { - dumpMemItems(pw, prefix + " ", mi.subitems, true); + dumpMemItems(pw, prefix + " ", mi.shortLabel, mi.subitems, + true, isCompact); } } } @@ -10634,23 +11679,37 @@ public final class ActivityManagerService extends ActivityManagerNative } static final int[] DUMP_MEM_OOM_ADJ = new int[] { + ProcessList.NATIVE_ADJ, ProcessList.SYSTEM_ADJ, ProcessList.PERSISTENT_PROC_ADJ, ProcessList.FOREGROUND_APP_ADJ, - ProcessList.VISIBLE_APP_ADJ, ProcessList.PERCEPTIBLE_APP_ADJ, ProcessList.HEAVY_WEIGHT_APP_ADJ, - ProcessList.BACKUP_APP_ADJ, ProcessList.SERVICE_ADJ, ProcessList.HOME_APP_ADJ, - ProcessList.PREVIOUS_APP_ADJ, ProcessList.SERVICE_B_ADJ, ProcessList.HIDDEN_APP_MAX_ADJ + ProcessList.VISIBLE_APP_ADJ, ProcessList.PERCEPTIBLE_APP_ADJ, + ProcessList.BACKUP_APP_ADJ, ProcessList.HEAVY_WEIGHT_APP_ADJ, + ProcessList.SERVICE_ADJ, ProcessList.HOME_APP_ADJ, + ProcessList.PREVIOUS_APP_ADJ, ProcessList.SERVICE_B_ADJ, ProcessList.CACHED_APP_MAX_ADJ }; static final String[] DUMP_MEM_OOM_LABEL = new String[] { + "Native", "System", "Persistent", "Foreground", - "Visible", "Perceptible", "Heavy Weight", - "Backup", "A Services", "Home", "Previous", - "B Services", "Background" + "Visible", "Perceptible", + "Heavy Weight", "Backup", + "A Services", "Home", + "Previous", "B Services", "Cached" + }; + static final String[] DUMP_MEM_OOM_COMPACT_LABEL = new String[] { + "native", + "sys", "pers", "fore", + "vis", "percept", + "heavy", "backup", + "servicea", "home", + "prev", "serviceb", "cached" }; final void dumpApplicationMemoryUsage(FileDescriptor fd, - PrintWriter pw, String prefix, String[] args, boolean brief, - PrintWriter categoryPw, StringBuilder outTag, StringBuilder outStack) { - boolean dumpAll = false; + PrintWriter pw, String prefix, String[] args, boolean brief, PrintWriter categoryPw) { + boolean dumpDetails = false; + boolean dumpFullDetails = false; + boolean dumpDalvik = false; boolean oomOnly = false; + boolean isCompact = false; int opti = 0; while (opti < args.length) { @@ -10660,12 +11719,20 @@ public final class ActivityManagerService extends ActivityManagerNative } opti++; if ("-a".equals(opt)) { - dumpAll = true; + dumpDetails = true; + dumpFullDetails = true; + dumpDalvik = true; + } else if ("-d".equals(opt)) { + dumpDalvik = true; + } else if ("-c".equals(opt)) { + isCompact = true; } else if ("--oom".equals(opt)) { oomOnly = true; } else if ("-h".equals(opt)) { - pw.println("meminfo dump options: [-a] [--oom] [process]"); + pw.println("meminfo dump options: [-a] [-d] [-c] [--oom] [process]"); pw.println(" -a: include all available information for each process."); + pw.println(" -d: include dalvik details when dumping process details."); + pw.println(" -c: dump in a compact machine-parseable representation."); pw.println(" --oom: only show processes organized by oom adj."); pw.println("If [process] is specified it can be the name or "); pw.println("pid of a specific process to dump."); @@ -10684,14 +11751,13 @@ public final class ActivityManagerService extends ActivityManagerNative long uptime = SystemClock.uptimeMillis(); long realtime = SystemClock.elapsedRealtime(); - if (procs.size() == 1 || isCheckinRequest) { - dumpAll = true; + if (!brief && !oomOnly && (procs.size() == 1 || isCheckinRequest)) { + dumpDetails = true; } - if (isCheckinRequest) { + if (isCheckinRequest || isCompact) { // short checkin version - pw.println(uptime + "," + realtime); - pw.flush(); + pw.print("time,"); pw.print(uptime); pw.print(","); pw.println(realtime); } else { pw.println("Applications Memory Usage (kB):"); pw.println("Uptime: " + uptime + " Realtime: " + realtime); @@ -10701,43 +11767,74 @@ public final class ActivityManagerService extends ActivityManagerNative System.arraycopy(args, opti, innerArgs, 0, args.length-opti); ArrayList<MemItem> procMems = new ArrayList<MemItem>(); + final SparseArray<MemItem> procMemsMap = new SparseArray<MemItem>(); long nativePss=0, dalvikPss=0, otherPss=0; long[] miscPss = new long[Debug.MemoryInfo.NUM_OTHER_STATS]; long oomPss[] = new long[DUMP_MEM_OOM_LABEL.length]; ArrayList<MemItem>[] oomProcs = (ArrayList<MemItem>[]) new ArrayList[DUMP_MEM_OOM_LABEL.length]; + final long[] tmpLong = new long[1]; long totalPss = 0; + long cachedPss = 0; + Debug.MemoryInfo mi = null; for (int i = procs.size() - 1 ; i >= 0 ; i--) { - ProcessRecord r = procs.get(i); - if (r.thread != null) { - if (!isCheckinRequest && dumpAll) { - pw.println("\n** MEMINFO in pid " + r.pid + " [" + r.processName + "] **"); - pw.flush(); + final ProcessRecord r = procs.get(i); + final IApplicationThread thread; + final int pid; + final int oomAdj; + final boolean hasActivities; + synchronized (this) { + thread = r.thread; + pid = r.pid; + oomAdj = r.getSetAdjWithServices(); + hasActivities = r.hasActivities; + } + if (thread != null) { + if (!isCheckinRequest && dumpDetails) { + pw.println("\n** MEMINFO in pid " + pid + " [" + r.processName + "] **"); } - Debug.MemoryInfo mi = null; - if (dumpAll) { + if (mi == null) { + mi = new Debug.MemoryInfo(); + } + if (dumpDetails || (!brief && !oomOnly)) { + Debug.getMemoryInfo(pid, mi); + } else { + mi.dalvikPss = (int)Debug.getPss(pid, tmpLong); + mi.dalvikPrivateDirty = (int)tmpLong[0]; + } + if (dumpDetails) { try { - mi = r.thread.dumpMemInfo(fd, isCheckinRequest, dumpAll, innerArgs); + pw.flush(); + thread.dumpMemInfo(fd, mi, isCheckinRequest, dumpFullDetails, + dumpDalvik, innerArgs); } catch (RemoteException e) { if (!isCheckinRequest) { pw.println("Got RemoteException!"); pw.flush(); } } - } else { - mi = new Debug.MemoryInfo(); - Debug.getMemoryInfo(r.pid, mi); + } + + final long myTotalPss = mi.getTotalPss(); + final long myTotalUss = mi.getTotalUss(); + + synchronized (this) { + if (r.thread != null && oomAdj == r.getSetAdjWithServices()) { + // Record this for posterity if the process has been stable. + r.baseProcessTracker.addPss(myTotalPss, myTotalUss, true, r.pkgList); + } } if (!isCheckinRequest && mi != null) { - long myTotalPss = mi.getTotalPss(); totalPss += myTotalPss; - MemItem pssItem = new MemItem(r.processName + " (pid " + r.pid + ")", - r.processName, myTotalPss, 0); + MemItem pssItem = new MemItem(r.processName + " (pid " + pid + + (hasActivities ? " / activities)" : ")"), + r.processName, myTotalPss, pid, hasActivities); procMems.add(pssItem); + procMemsMap.put(pid, pssItem); nativePss += mi.nativePss; dalvikPss += mi.dalvikPss; @@ -10748,8 +11845,12 @@ public final class ActivityManagerService extends ActivityManagerNative otherPss -= mem; } + if (oomAdj >= ProcessList.CACHED_APP_MIN_ADJ) { + cachedPss += myTotalPss; + } + for (int oomIndex=0; oomIndex<oomPss.length; oomIndex++) { - if (r.setAdj <= DUMP_MEM_OOM_ADJ[oomIndex] + if (oomAdj <= DUMP_MEM_OOM_ADJ[oomIndex] || oomIndex == (oomPss.length-1)) { oomPss[oomIndex] += myTotalPss; if (oomProcs[oomIndex] == null) { @@ -10764,6 +11865,48 @@ public final class ActivityManagerService extends ActivityManagerNative } if (!isCheckinRequest && procs.size() > 1) { + // If we are showing aggregations, also look for native processes to + // include so that our aggregations are more accurate. + updateCpuStatsNow(); + synchronized (mProcessCpuThread) { + final int N = mProcessCpuTracker.countStats(); + for (int i=0; i<N; i++) { + ProcessCpuTracker.Stats st = mProcessCpuTracker.getStats(i); + if (st.vsize > 0 && procMemsMap.indexOfKey(st.pid) < 0) { + if (mi == null) { + mi = new Debug.MemoryInfo(); + } + if (!brief && !oomOnly) { + Debug.getMemoryInfo(st.pid, mi); + } else { + mi.nativePss = (int)Debug.getPss(st.pid, tmpLong); + mi.nativePrivateDirty = (int)tmpLong[0]; + } + + final long myTotalPss = mi.getTotalPss(); + totalPss += myTotalPss; + + MemItem pssItem = new MemItem(st.name + " (pid " + st.pid + ")", + st.name, myTotalPss, st.pid, false); + procMems.add(pssItem); + + nativePss += mi.nativePss; + dalvikPss += mi.dalvikPss; + otherPss += mi.otherPss; + for (int j=0; j<Debug.MemoryInfo.NUM_OTHER_STATS; j++) { + long mem = mi.getOtherPss(j); + miscPss[j] += mem; + otherPss -= mem; + } + oomPss[0] += myTotalPss; + if (oomProcs[0] == null) { + oomProcs[0] = new ArrayList<MemItem>(); + } + oomProcs[0].add(pssItem); + } + } + } + ArrayList<MemItem> catMems = new ArrayList<MemItem>(); catMems.add(new MemItem("Native", "Native", nativePss, -1)); @@ -10777,7 +11920,8 @@ public final class ActivityManagerService extends ActivityManagerNative ArrayList<MemItem> oomMems = new ArrayList<MemItem>(); for (int j=0; j<oomPss.length; j++) { if (oomPss[j] != 0) { - String label = DUMP_MEM_OOM_LABEL[j]; + String label = isCompact ? DUMP_MEM_OOM_COMPACT_LABEL[j] + : DUMP_MEM_OOM_LABEL[j]; MemItem item = new MemItem(label, label, oomPss[j], DUMP_MEM_OOM_ADJ[j]); item.subitems = oomProcs[j]; @@ -10785,107 +11929,137 @@ public final class ActivityManagerService extends ActivityManagerNative } } - if (outTag != null || outStack != null) { - if (outTag != null) { - appendMemBucket(outTag, totalPss, "total", false); - } - if (outStack != null) { - appendMemBucket(outStack, totalPss, "total", true); - } - boolean firstLine = true; - for (int i=0; i<oomMems.size(); i++) { - MemItem miCat = oomMems.get(i); - if (miCat.subitems == null || miCat.subitems.size() < 1) { - continue; - } - if (miCat.id < ProcessList.SERVICE_ADJ - || miCat.id == ProcessList.HOME_APP_ADJ - || miCat.id == ProcessList.PREVIOUS_APP_ADJ) { - if (outTag != null && miCat.id <= ProcessList.FOREGROUND_APP_ADJ) { - outTag.append(" / "); - } - if (outStack != null) { - if (miCat.id >= ProcessList.FOREGROUND_APP_ADJ) { - if (firstLine) { - outStack.append(":"); - firstLine = false; - } - outStack.append("\n\t at "); - } else { - outStack.append("$"); - } - } - for (int j=0; j<miCat.subitems.size(); j++) { - MemItem mi = miCat.subitems.get(j); - if (j > 0) { - if (outTag != null) { - outTag.append(" "); - } - if (outStack != null) { - outStack.append("$"); - } - } - if (outTag != null && miCat.id <= ProcessList.FOREGROUND_APP_ADJ) { - appendMemBucket(outTag, mi.pss, mi.shortLabel, false); - } - if (outStack != null) { - appendMemBucket(outStack, mi.pss, mi.shortLabel, true); - } - } - if (outStack != null && miCat.id >= ProcessList.FOREGROUND_APP_ADJ) { - outStack.append("("); - for (int k=0; k<DUMP_MEM_OOM_ADJ.length; k++) { - if (DUMP_MEM_OOM_ADJ[k] == miCat.id) { - outStack.append(DUMP_MEM_OOM_LABEL[k]); - outStack.append(":"); - outStack.append(DUMP_MEM_OOM_ADJ[k]); - } - } - outStack.append(")"); - } - } - } - } - - if (!brief && !oomOnly) { + if (!brief && !oomOnly && !isCompact) { pw.println(); pw.println("Total PSS by process:"); - dumpMemItems(pw, " ", procMems, true); + dumpMemItems(pw, " ", "proc", procMems, true, isCompact); pw.println(); } - pw.println("Total PSS by OOM adjustment:"); - dumpMemItems(pw, " ", oomMems, false); - if (!oomOnly) { + if (!isCompact) { + pw.println("Total PSS by OOM adjustment:"); + } + dumpMemItems(pw, " ", "oom", oomMems, false, isCompact); + if (!brief && !oomOnly) { PrintWriter out = categoryPw != null ? categoryPw : pw; - out.println(); - out.println("Total PSS by category:"); - dumpMemItems(out, " ", catMems, true); + if (!isCompact) { + out.println(); + out.println("Total PSS by category:"); + } + dumpMemItems(out, " ", "cat", catMems, true, isCompact); + } + if (!isCompact) { + pw.println(); + } + MemInfoReader memInfo = new MemInfoReader(); + memInfo.readMemInfo(); + if (!brief) { + if (!isCompact) { + pw.print("Total RAM: "); pw.print(memInfo.getTotalSizeKb()); + pw.println(" kB"); + pw.print(" Free RAM: "); pw.print(cachedPss + memInfo.getCachedSizeKb() + + memInfo.getFreeSizeKb()); pw.print(" kB ("); + pw.print(cachedPss); pw.print(" cached pss + "); + pw.print(memInfo.getCachedSizeKb()); pw.print(" cached + "); + pw.print(memInfo.getFreeSizeKb()); pw.println(" free)"); + } else { + pw.print("ram,"); pw.print(memInfo.getTotalSizeKb()); pw.print(","); + pw.print(cachedPss + memInfo.getCachedSizeKb() + + memInfo.getFreeSizeKb()); pw.print(","); + pw.println(totalPss - cachedPss); + } + } + if (!isCompact) { + pw.print(" Used RAM: "); pw.print(totalPss - cachedPss + + memInfo.getBuffersSizeKb() + memInfo.getShmemSizeKb() + + memInfo.getSlabSizeKb()); pw.print(" kB ("); + pw.print(totalPss - cachedPss); pw.print(" used pss + "); + pw.print(memInfo.getBuffersSizeKb()); pw.print(" buffers + "); + pw.print(memInfo.getShmemSizeKb()); pw.print(" shmem + "); + pw.print(memInfo.getSlabSizeKb()); pw.println(" slab)"); + pw.print(" Lost RAM: "); pw.print(memInfo.getTotalSizeKb() + - totalPss - memInfo.getFreeSizeKb() - memInfo.getCachedSizeKb() + - memInfo.getBuffersSizeKb() - memInfo.getShmemSizeKb() + - memInfo.getSlabSizeKb()); pw.println(" kB"); + } + if (!brief) { + if (memInfo.getZramTotalSizeKb() != 0) { + if (!isCompact) { + pw.print(" ZRAM: "); pw.print(memInfo.getZramTotalSizeKb()); + pw.print(" kB physical used for "); + pw.print(memInfo.getSwapTotalSizeKb() + - memInfo.getSwapFreeSizeKb()); + pw.print(" kB in swap ("); + pw.print(memInfo.getSwapTotalSizeKb()); + pw.println(" kB total swap)"); + } else { + pw.print("zram,"); pw.print(memInfo.getZramTotalSizeKb()); pw.print(","); + pw.print(memInfo.getSwapTotalSizeKb()); pw.print(","); + pw.println(memInfo.getSwapFreeSizeKb()); + } + } + final int[] SINGLE_LONG_FORMAT = new int[] { + Process.PROC_SPACE_TERM|Process.PROC_OUT_LONG + }; + long[] longOut = new long[1]; + Process.readProcFile("/sys/kernel/mm/ksm/pages_shared", + SINGLE_LONG_FORMAT, null, longOut, null); + long shared = longOut[0] * ProcessList.PAGE_SIZE / 1024; + longOut[0] = 0; + Process.readProcFile("/sys/kernel/mm/ksm/pages_sharing", + SINGLE_LONG_FORMAT, null, longOut, null); + long sharing = longOut[0] * ProcessList.PAGE_SIZE / 1024; + longOut[0] = 0; + Process.readProcFile("/sys/kernel/mm/ksm/pages_unshared", + SINGLE_LONG_FORMAT, null, longOut, null); + long unshared = longOut[0] * ProcessList.PAGE_SIZE / 1024; + longOut[0] = 0; + Process.readProcFile("/sys/kernel/mm/ksm/pages_volatile", + SINGLE_LONG_FORMAT, null, longOut, null); + long voltile = longOut[0] * ProcessList.PAGE_SIZE / 1024; + if (!isCompact) { + if (sharing != 0 || shared != 0 || unshared != 0 || voltile != 0) { + pw.print(" KSM: "); pw.print(sharing); + pw.print(" kB saved from shared "); + pw.print(shared); pw.println(" kB"); + pw.print(" "); pw.print(unshared); pw.print(" kB unshared; "); + pw.print(voltile); pw.println(" kB volatile"); + } + pw.print(" Tuning: "); + pw.print(ActivityManager.staticGetMemoryClass()); + pw.print(" (large "); + pw.print(ActivityManager.staticGetLargeMemoryClass()); + pw.print("), oom "); + pw.print(mProcessList.getMemLevel(ProcessList.CACHED_APP_MAX_ADJ)/1024); + pw.print(" kB"); + pw.print(", restore limit "); + pw.print(mProcessList.getCachedRestoreThresholdKb()); + pw.print(" kB"); + if (ActivityManager.isLowRamDeviceStatic()) { + pw.print(" (low-ram)"); + } + if (ActivityManager.isHighEndGfx()) { + pw.print(" (high-end-gfx)"); + } + pw.println(); + } else { + pw.print("ksm,"); pw.print(sharing); pw.print(","); + pw.print(shared); pw.print(","); pw.print(unshared); pw.print(","); + pw.println(voltile); + pw.print("tuning,"); + pw.print(ActivityManager.staticGetMemoryClass()); + pw.print(','); + pw.print(ActivityManager.staticGetLargeMemoryClass()); + pw.print(','); + pw.print(mProcessList.getMemLevel(ProcessList.CACHED_APP_MAX_ADJ)/1024); + if (ActivityManager.isLowRamDeviceStatic()) { + pw.print(",low-ram"); + } + if (ActivityManager.isHighEndGfx()) { + pw.print(",high-end-gfx"); + } + pw.println(); + } } - pw.println(); - pw.print("Total PSS: "); pw.print(totalPss); pw.println(" kB"); - final int[] SINGLE_LONG_FORMAT = new int[] { - Process.PROC_SPACE_TERM|Process.PROC_OUT_LONG - }; - long[] longOut = new long[1]; - Process.readProcFile("/sys/kernel/mm/ksm/pages_shared", - SINGLE_LONG_FORMAT, null, longOut, null); - long shared = longOut[0] * ProcessList.PAGE_SIZE / 1024; - longOut[0] = 0; - Process.readProcFile("/sys/kernel/mm/ksm/pages_sharing", - SINGLE_LONG_FORMAT, null, longOut, null); - long sharing = longOut[0] * ProcessList.PAGE_SIZE / 1024; - longOut[0] = 0; - Process.readProcFile("/sys/kernel/mm/ksm/pages_unshared", - SINGLE_LONG_FORMAT, null, longOut, null); - long unshared = longOut[0] * ProcessList.PAGE_SIZE / 1024; - longOut[0] = 0; - Process.readProcFile("/sys/kernel/mm/ksm/pages_volatile", - SINGLE_LONG_FORMAT, null, longOut, null); - long voltile = longOut[0] * ProcessList.PAGE_SIZE / 1024; - pw.print(" KSM: "); pw.print(sharing); pw.print(" kB saved from shared "); - pw.print(shared); pw.println(" kB"); - pw.print(" "); pw.print(unshared); pw.print(" kB unshared; "); - pw.print(voltile); pw.println(" kB volatile"); } } @@ -10938,13 +12112,9 @@ public final class ActivityManagerService extends ActivityManagerNative if (!capp.persistent && capp.thread != null && capp.pid != 0 && capp.pid != MY_PID) { - Slog.i(TAG, "Kill " + capp.processName - + " (pid " + capp.pid + "): provider " + cpr.info.name - + " in dying process " + (proc != null ? proc.processName : "??")); - EventLog.writeEvent(EventLogTags.AM_KILL, capp.userId, capp.pid, - capp.processName, capp.setAdj, "dying provider " - + cpr.name.toShortString()); - Process.killProcessQuiet(capp.pid); + killUnneededProcessLocked(capp, "depends on provider " + + cpr.name.flattenToShortString() + + " in dying proc " + (proc != null ? proc.processName : "??")); } } else if (capp.thread != null && conn.provider.provider != null) { try { @@ -10963,20 +12133,21 @@ public final class ActivityManagerService extends ActivityManagerNative } return inLaunching; } - + /** * Main code for cleaning up a process when it has gone away. This is - * called both as a result of the process dying, or directly when stopping + * called both as a result of the process dying, or directly when stopping * a process when running in single process mode. */ private final void cleanUpApplicationRecordLocked(ProcessRecord app, boolean restarting, boolean allowRestart, int index) { if (index >= 0) { - mLruProcesses.remove(index); + removeLruProcessLocked(app); } mProcessesToGc.remove(app); - + mPendingPssProcesses.remove(app); + // Dismiss any open dialogs. if (app.crashDialog != null && !app.forceCrashReport) { app.crashDialog.dismiss(); @@ -10993,10 +12164,10 @@ public final class ActivityManagerService extends ActivityManagerNative app.crashing = false; app.notResponding = false; - - app.resetPackageList(); + + app.resetPackageList(mProcessStats); app.unlinkDeathRecipient(); - app.thread = null; + app.makeInactive(mProcessStats); app.forcingToForeground = null; app.foregroundServices = false; app.foregroundActivities = false; @@ -11008,29 +12179,25 @@ public final class ActivityManagerService extends ActivityManagerNative boolean restart = false; // Remove published content providers. - if (!app.pubProviders.isEmpty()) { - Iterator<ContentProviderRecord> it = app.pubProviders.values().iterator(); - while (it.hasNext()) { - ContentProviderRecord cpr = it.next(); - - final boolean always = app.bad || !allowRestart; - if (removeDyingProviderLocked(app, cpr, always) || always) { - // We left the provider in the launching list, need to - // restart it. - restart = true; - } - - cpr.provider = null; - cpr.proc = null; + for (int i=app.pubProviders.size()-1; i>=0; i--) { + ContentProviderRecord cpr = app.pubProviders.valueAt(i); + final boolean always = app.bad || !allowRestart; + if (removeDyingProviderLocked(app, cpr, always) || always) { + // We left the provider in the launching list, need to + // restart it. + restart = true; } - app.pubProviders.clear(); + + cpr.provider = null; + cpr.proc = null; } - + app.pubProviders.clear(); + // Take care of any launching providers waiting for this process. if (checkAppInLaunchingProvidersLocked(app, false)) { restart = true; } - + // Unregister from connected content providers. if (!app.conProviders.isEmpty()) { for (int i=0; i<app.conProviders.size(); i++) { @@ -11057,18 +12224,15 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + skipCurrentReceiverLocked(app); // Unregister any receivers. - if (app.receivers.size() > 0) { - Iterator<ReceiverList> it = app.receivers.iterator(); - while (it.hasNext()) { - removeReceiverLocked(it.next()); - } - app.receivers.clear(); + for (int i=app.receivers.size()-1; i>=0; i--) { + removeReceiverLocked(app.receivers.valueAt(i)); } - + app.receivers.clear(); + // If the app is undergoing backup, tell the backup manager about it if (mBackupTarget != null && app.pid == mBackupTarget.app.pid) { if (DEBUG_BACKUP || DEBUG_CLEANUP) Slog.d(TAG, "App " @@ -11435,7 +12599,7 @@ public final class ActivityManagerService extends ActivityManagerNative : new ComponentName("android", "FullBackupAgent"); // startProcessLocked() returns existing proc's record if it's already running ProcessRecord proc = startProcessLocked(app.processName, app, - false, 0, "backup", hostingName, false, false); + false, 0, "backup", hostingName, false, false, false); if (proc == null) { Slog.e(TAG, "Unable to start backup agent process " + r); return false; @@ -11555,7 +12719,7 @@ public final class ActivityManagerService extends ActivityManagerNative private final List getStickiesLocked(String action, IntentFilter filter, List cur, int userId) { final ContentResolver resolver = mContext.getContentResolver(); - HashMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId); + ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId); if (stickies == null) { return cur; } @@ -11613,7 +12777,7 @@ public final class ActivityManagerService extends ActivityManagerNative + ") when registering receiver " + receiver); } if (callerApp.info.uid != Process.SYSTEM_UID && - !callerApp.pkgList.contains(callerPackage)) { + !callerApp.pkgList.containsKey(callerPackage)) { throw new SecurityException("Given caller package " + callerPackage + " is not running in process " + callerApp); } @@ -11706,7 +12870,7 @@ public final class ActivityManagerService extends ActivityManagerNative Intent intent = (Intent)allSticky.get(i); BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, null, - null, -1, -1, null, AppOpsManager.OP_NONE, receivers, null, 0, + null, -1, -1, null, null, AppOpsManager.OP_NONE, receivers, null, 0, null, null, false, true, true, -1); queue.enqueueParallelBroadcastLocked(r); queue.scheduleBroadcastsLocked(); @@ -11725,14 +12889,13 @@ public final class ActivityManagerService extends ActivityManagerNative boolean doTrim = false; synchronized(this) { - ReceiverList rl - = (ReceiverList)mRegisteredReceivers.get(receiver.asBinder()); + ReceiverList rl = mRegisteredReceivers.get(receiver.asBinder()); if (rl != null) { if (rl.curBroadcast != null) { BroadcastRecord r = rl.curBroadcast; final boolean doNext = finishReceiverLocked( receiver.asBinder(), r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); + r.resultExtras, r.resultAbort); if (doNext) { doTrim = true; r.queue.processNextBroadcast(false); @@ -11967,7 +13130,8 @@ public final class ActivityManagerService extends ActivityManagerNative String list[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); if (list != null && (list.length > 0)) { for (String pkg : list) { - forceStopPackageLocked(pkg, -1, false, true, true, false, userId); + forceStopPackageLocked(pkg, -1, false, true, true, false, userId, + "storage unmount"); } sendPackageBroadcastLocked( IApplicationThread.EXTERNAL_STORAGE_UNAVAILABLE, list, userId); @@ -11976,17 +13140,22 @@ public final class ActivityManagerService extends ActivityManagerNative Uri data = intent.getData(); String ssp; if (data != null && (ssp=data.getSchemeSpecificPart()) != null) { + boolean removed = Intent.ACTION_PACKAGE_REMOVED.equals( + intent.getAction()); if (!intent.getBooleanExtra(Intent.EXTRA_DONT_KILL_APP, false)) { - forceStopPackageLocked(ssp, - intent.getIntExtra(Intent.EXTRA_UID, -1), false, true, true, - false, userId); + forceStopPackageLocked(ssp, UserHandle.getAppId( + intent.getIntExtra(Intent.EXTRA_UID, -1)), false, true, true, + false, userId, removed ? "pkg removed" : "pkg changed"); } - if (Intent.ACTION_PACKAGE_REMOVED.equals(intent.getAction())) { + if (removed) { sendPackageBroadcastLocked(IApplicationThread.PACKAGE_REMOVED, new String[] {ssp}, userId); if (!intent.getBooleanExtra(Intent.EXTRA_REPLACING, false)) { mAppOpsService.packageRemoved( intent.getIntExtra(Intent.EXTRA_UID, -1), ssp); + + // Remove all permissions granted from/to this package + removeUriPermissionsForPackageLocked(ssp, userId, true); } } } @@ -12023,12 +13192,12 @@ public final class ActivityManagerService extends ActivityManagerNative } if (intent.ACTION_CLEAR_DNS_CACHE.equals(intent.getAction())) { - mHandler.sendEmptyMessage(CLEAR_DNS_CACHE); + mHandler.sendEmptyMessage(CLEAR_DNS_CACHE_MSG); } if (Proxy.PROXY_CHANGE_ACTION.equals(intent.getAction())) { ProxyProperties proxy = intent.getParcelableExtra("proxy"); - mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY, proxy)); + mHandler.sendMessage(mHandler.obtainMessage(UPDATE_HTTP_PROXY_MSG, proxy)); } // Add to the sticky list if requested. @@ -12057,7 +13226,7 @@ public final class ActivityManagerService extends ActivityManagerNative // But first, if this is not a broadcast to all users, then // make sure it doesn't conflict with an existing broadcast to // all users. - HashMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get( + ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get( UserHandle.USER_ALL); if (stickies != null) { ArrayList<Intent> list = stickies.get(intent.getAction()); @@ -12074,9 +13243,9 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - HashMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId); + ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId); if (stickies == null) { - stickies = new HashMap<String, ArrayList<Intent>>(); + stickies = new ArrayMap<String, ArrayList<Intent>>(); mStickyBroadcasts.put(userId, stickies); } ArrayList<Intent> list = stickies.get(intent.getAction()); @@ -12133,8 +13302,8 @@ public final class ActivityManagerService extends ActivityManagerNative // components to be launched. final BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, - callerPackage, callingPid, callingUid, requiredPermission, appOp, - registeredReceivers, resultTo, resultCode, resultData, map, + callerPackage, callingPid, callingUid, resolvedType, requiredPermission, + appOp, registeredReceivers, resultTo, resultCode, resultData, map, ordered, sticky, false, userId); if (DEBUG_BROADCAST) Slog.v( TAG, "Enqueueing parallel broadcast " + r); @@ -12223,9 +13392,9 @@ public final class ActivityManagerService extends ActivityManagerNative || resultTo != null) { BroadcastQueue queue = broadcastQueueForIntent(intent); BroadcastRecord r = new BroadcastRecord(queue, intent, callerApp, - callerPackage, callingPid, callingUid, requiredPermission, appOp, - receivers, resultTo, resultCode, resultData, map, ordered, - sticky, false, userId); + callerPackage, callingPid, callingUid, resolvedType, + requiredPermission, appOp, receivers, resultTo, resultCode, + resultData, map, ordered, sticky, false, userId); if (DEBUG_BROADCAST) Slog.v( TAG, "Enqueueing ordered broadcast " + r + ": prev had " + queue.mOrderedBroadcasts.size()); @@ -12329,7 +13498,7 @@ public final class ActivityManagerService extends ActivityManagerNative Slog.w(TAG, msg); throw new SecurityException(msg); } - HashMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId); + ArrayMap<String, ArrayList<Intent>> stickies = mStickyBroadcasts.get(userId); if (stickies != null) { ArrayList<Intent> list = stickies.get(intent.getAction()); if (list != null) { @@ -12353,16 +13522,20 @@ public final class ActivityManagerService extends ActivityManagerNative } private final boolean finishReceiverLocked(IBinder receiver, int resultCode, - String resultData, Bundle resultExtras, boolean resultAbort, - boolean explicit) { + String resultData, Bundle resultExtras, boolean resultAbort) { final BroadcastRecord r = broadcastRecordForReceiverLocked(receiver); if (r == null) { Slog.w(TAG, "finishReceiver called but not found on queue"); return false; } - return r.queue.finishReceiverLocked(r, resultCode, resultData, resultExtras, resultAbort, - explicit); + return r.queue.finishReceiverLocked(r, resultCode, resultData, resultExtras, resultAbort, false); + } + + void backgroundServicesFinishedLocked(int userId) { + for (BroadcastQueue queue : mBroadcastQueues) { + queue.backgroundServicesFinishedLocked(userId); + } } public void finishReceiver(IBinder who, int resultCode, String resultData, @@ -12377,7 +13550,7 @@ public final class ActivityManagerService extends ActivityManagerNative final long origId = Binder.clearCallingIdentity(); try { boolean doNext = false; - BroadcastRecord r = null; + BroadcastRecord r; synchronized(this) { r = broadcastRecordForReceiverLocked(who); @@ -12450,7 +13623,8 @@ public final class ActivityManagerService extends ActivityManagerNative final long origId = Binder.clearCallingIdentity(); // Instrumentation can kill and relaunch even persistent processes - forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, userId); + forceStopPackageLocked(ii.targetPackage, -1, true, false, true, true, userId, + "start instr"); ProcessRecord app = addAppLocked(ai, false); app.instrumentationClass = className; app.instrumentationInfo = ai; @@ -12517,7 +13691,8 @@ public final class ActivityManagerService extends ActivityManagerNative app.instrumentationProfileFile = null; app.instrumentationArguments = null; - forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, app.userId); + forceStopPackageLocked(app.info.packageName, -1, false, false, true, true, app.userId, + "finished inst"); } public void finishInstrumentation(IApplicationThread target, @@ -12563,6 +13738,10 @@ public final class ActivityManagerService extends ActivityManagerNative return config; } + ActivityStack getFocusedStack() { + return mStackSupervisor.getFocusedStack(); + } + public Configuration getConfiguration() { Configuration ci; synchronized(this) { @@ -12624,9 +13803,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (mHeadless) return true; int changes = 0; - - boolean kept = true; - + if (values != null) { Configuration newConfig = new Configuration(mConfiguration); changes = newConfig.updateFrom(values); @@ -12704,25 +13881,27 @@ public final class ActivityManagerService extends ActivityManagerNative } } } - + + boolean kept = true; + final ActivityStack mainStack = mStackSupervisor.getFocusedStack(); if (changes != 0 && starting == null) { // If the configuration changed, and the caller is not already // in the process of starting an activity, then find the top // activity to check if its configuration needs to change. - starting = mMainStack.topRunningActivityLocked(null); + starting = mainStack.topRunningActivityLocked(null); } - + if (starting != null) { - kept = mMainStack.ensureActivityConfigurationLocked(starting, changes); + kept = mainStack.ensureActivityConfigurationLocked(starting, changes); // And we need to make sure at this point that all other activities // are made visible with the correct configuration. - mMainStack.ensureActivitiesVisibleLocked(starting, changes); + mStackSupervisor.ensureActivitiesVisibleLocked(starting, changes); } - + if (values != null && mWindowManager != null) { mWindowManager.setNewConfiguration(mConfiguration); } - + return kept; } @@ -12738,7 +13917,7 @@ public final class ActivityManagerService extends ActivityManagerNative return !(config.keyboard == Configuration.KEYBOARD_NOKEYS && config.touchscreen == Configuration.TOUCHSCREEN_NOTOUCH); } - + /** * Save the locale. You must be inside a synchronized (this) block. */ @@ -12764,96 +13943,13 @@ public final class ActivityManagerService extends ActivityManagerNative public boolean navigateUpTo(IBinder token, Intent destIntent, int resultCode, Intent resultData) { - ComponentName dest = destIntent.getComponent(); synchronized (this) { - ActivityRecord srec = ActivityRecord.forToken(token); - if (srec == null) { - return false; - } - ArrayList<ActivityRecord> history = srec.stack.mHistory; - final int start = history.indexOf(srec); - if (start < 0) { - // Current activity is not in history stack; do nothing. - return false; + final ActivityStack stack = ActivityRecord.getStackLocked(token); + if (stack != null) { + return stack.navigateUpToLocked(token, destIntent, resultCode, resultData); } - int finishTo = start - 1; - ActivityRecord parent = null; - boolean foundParentInTask = false; - if (dest != null) { - TaskRecord tr = srec.task; - for (int i = start - 1; i >= 0; i--) { - ActivityRecord r = history.get(i); - if (tr != r.task) { - // Couldn't find parent in the same task; stop at the one above this. - // (Root of current task; in-app "home" behavior) - // Always at least finish the current activity. - finishTo = Math.min(start - 1, i + 1); - parent = history.get(finishTo); - break; - } else if (r.info.packageName.equals(dest.getPackageName()) && - r.info.name.equals(dest.getClassName())) { - finishTo = i; - parent = r; - foundParentInTask = true; - break; - } - } - } - - if (mController != null) { - ActivityRecord next = mMainStack.topRunningActivityLocked(token, 0); - if (next != null) { - // ask watcher if this is allowed - boolean resumeOK = true; - try { - resumeOK = mController.activityResuming(next.packageName); - } catch (RemoteException e) { - mController = null; - Watchdog.getInstance().setActivityController(null); - } - - if (!resumeOK) { - return false; - } - } - } - final long origId = Binder.clearCallingIdentity(); - for (int i = start; i > finishTo; i--) { - ActivityRecord r = history.get(i); - mMainStack.requestFinishActivityLocked(r.appToken, resultCode, resultData, - "navigate-up", true); - // Only return the supplied result for the first activity finished - resultCode = Activity.RESULT_CANCELED; - resultData = null; - } - - if (parent != null && foundParentInTask) { - final int parentLaunchMode = parent.info.launchMode; - final int destIntentFlags = destIntent.getFlags(); - if (parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE || - parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TASK || - parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TOP || - (destIntentFlags & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { - parent.deliverNewIntentLocked(srec.info.applicationInfo.uid, destIntent); - } else { - try { - ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo( - destIntent.getComponent(), 0, srec.userId); - int res = mMainStack.startActivityLocked(srec.app.thread, destIntent, - null, aInfo, parent.appToken, null, - 0, -1, parent.launchedFromUid, parent.launchedFromPackage, - 0, null, true, null); - foundParentInTask = res == ActivityManager.START_SUCCESS; - } catch (RemoteException e) { - foundParentInTask = false; - } - mMainStack.requestFinishActivityLocked(parent.appToken, resultCode, - resultData, "navigate-up", true); - } - } - Binder.restoreCallingIdentity(origId); - return foundParentInTask; + return false; } } @@ -12899,36 +13995,25 @@ public final class ActivityManagerService extends ActivityManagerNative return null; } - private final int computeOomAdjLocked(ProcessRecord app, int hiddenAdj, int clientHiddenAdj, - int emptyAdj, ProcessRecord TOP_APP, boolean recursed, boolean doingAll) { + private final int computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP, + boolean doingAll, long now) { if (mAdjSeq == app.adjSeq) { - // This adjustment has already been computed. If we are calling - // from the top, we may have already computed our adjustment with - // an earlier hidden adjustment that isn't really for us... if - // so, use the new hidden adjustment. - if (!recursed && app.hidden) { - if (app.hasActivities) { - app.curAdj = app.curRawAdj = app.nonStoppingAdj = hiddenAdj; - } else if (app.hasClientActivities) { - app.curAdj = app.curRawAdj = app.nonStoppingAdj = clientHiddenAdj; - } else { - app.curAdj = app.curRawAdj = app.nonStoppingAdj = emptyAdj; - } - } + // This adjustment has already been computed. return app.curRawAdj; } if (app.thread == null) { app.adjSeq = mAdjSeq; app.curSchedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; - return (app.curAdj=app.curRawAdj=ProcessList.HIDDEN_APP_MAX_ADJ); + app.curProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; + return (app.curAdj=app.curRawAdj=ProcessList.CACHED_APP_MAX_ADJ); } app.adjTypeCode = ActivityManager.RunningAppProcessInfo.REASON_UNKNOWN; app.adjSource = null; app.adjTarget = null; app.empty = false; - app.hidden = false; + app.cached = false; app.hasClientActivities = false; final int activitiesSize = app.activities.size(); @@ -12938,11 +14023,12 @@ public final class ActivityManagerService extends ActivityManagerNative // below foreground, so it is not worth doing work for it. app.adjType = "fixed"; app.adjSeq = mAdjSeq; - app.curRawAdj = app.nonStoppingAdj = app.maxAdj; + app.curRawAdj = app.maxAdj; app.hasActivities = false; app.foregroundActivities = false; app.keeping = true; app.curSchedGroup = Process.THREAD_GROUP_DEFAULT; + app.curProcState = ActivityManager.PROCESS_STATE_PERSISTENT; // System process can do UI, and when they do we want to have // them trim their memory after the user leaves the UI. To // facilitate this, here we need to determine whether or not it @@ -12962,6 +14048,9 @@ public final class ActivityManagerService extends ActivityManagerNative } } } + if (!app.systemNoUi) { + app.curProcState = ActivityManager.PROCESS_STATE_PERSISTENT_UI; + } return (app.curAdj=app.maxAdj); } @@ -12973,6 +14062,7 @@ public final class ActivityManagerService extends ActivityManagerNative // important to least, and assign an appropriate OOM adjustment. int adj; int schedGroup; + int procState; boolean foregroundActivities = false; boolean interesting = false; BroadcastQueue queue; @@ -12984,12 +14074,14 @@ public final class ActivityManagerService extends ActivityManagerNative foregroundActivities = true; interesting = true; app.hasActivities = true; + procState = ActivityManager.PROCESS_STATE_TOP; } else if (app.instrumentationClass != null) { // Don't want to kill running instrumentation. adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = Process.THREAD_GROUP_DEFAULT; app.adjType = "instrumentation"; interesting = true; + procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; } else if ((queue = isReceivingBroadcast(app)) != null) { // An app that is currently receiving a broadcast also // counts as being in the foreground for OOM killer purposes. @@ -12999,36 +14091,50 @@ public final class ActivityManagerService extends ActivityManagerNative schedGroup = (queue == mFgBroadcastQueue) ? Process.THREAD_GROUP_DEFAULT : Process.THREAD_GROUP_BG_NONINTERACTIVE; app.adjType = "broadcast"; + procState = ActivityManager.PROCESS_STATE_RECEIVER; } else if (app.executingServices.size() > 0) { // An app that is currently executing a service callback also // counts as being in the foreground. adj = ProcessList.FOREGROUND_APP_ADJ; - schedGroup = Process.THREAD_GROUP_DEFAULT; + schedGroup = app.execServicesFg ? + Process.THREAD_GROUP_DEFAULT : Process.THREAD_GROUP_BG_NONINTERACTIVE; app.adjType = "exec-service"; + procState = ActivityManager.PROCESS_STATE_SERVICE; + //Slog.i(TAG, "EXEC " + (app.execServicesFg ? "FG" : "BG") + ": " + app); } else { - // Assume process is hidden (has activities); we will correct - // later if this is not the case. - adj = hiddenAdj; + // As far as we know the process is empty. We may change our mind later. schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; - app.hidden = true; - app.adjType = "bg-act"; + // At this point we don't actually know the adjustment. Use the cached adj + // value that the caller wants us to. + adj = cachedAdj; + procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; + app.cached = true; + app.empty = true; + app.adjType = "cch-empty"; } - boolean hasStoppingActivities = false; - // Examine all activities if not already foreground. if (!foregroundActivities && activitiesSize > 0) { for (int j = 0; j < activitiesSize; j++) { final ActivityRecord r = app.activities.get(j); + if (r.app != app) { + Slog.w(TAG, "Wtf, activity " + r + " in proc activity list not using proc " + + app + "?!?"); + continue; + } + app.hasActivities = true; if (r.visible) { // App has a visible activity; only upgrade adjustment. if (adj > ProcessList.VISIBLE_APP_ADJ) { adj = ProcessList.VISIBLE_APP_ADJ; app.adjType = "visible"; } + if (procState > ActivityManager.PROCESS_STATE_TOP) { + procState = ActivityManager.PROCESS_STATE_TOP; + } schedGroup = Process.THREAD_GROUP_DEFAULT; - app.hidden = false; - app.hasActivities = true; + app.cached = false; + app.empty = false; foregroundActivities = true; break; } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { @@ -13036,46 +14142,55 @@ public final class ActivityManagerService extends ActivityManagerNative adj = ProcessList.PERCEPTIBLE_APP_ADJ; app.adjType = "pausing"; } - app.hidden = false; + if (procState > ActivityManager.PROCESS_STATE_TOP) { + procState = ActivityManager.PROCESS_STATE_TOP; + } + schedGroup = Process.THREAD_GROUP_DEFAULT; + app.cached = false; + app.empty = false; foregroundActivities = true; } else if (r.state == ActivityState.STOPPING) { - // We will apply the actual adjustment later, because - // we want to allow this process to immediately go through - // any memory trimming that is in effect. - app.hidden = false; + if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { + adj = ProcessList.PERCEPTIBLE_APP_ADJ; + app.adjType = "stopping"; + } + // For the process state, we will at this point consider the + // process to be cached. It will be cached either as an activity + // or empty depending on whether the activity is finishing. We do + // this so that we can treat the process as cached for purposes of + // memory trimming (determing current memory level, trim command to + // send to process) since there can be an arbitrary number of stopping + // processes and they should soon all go into the cached state. + if (!r.finishing) { + if (procState > ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) { + procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; + } + } + app.cached = false; + app.empty = false; foregroundActivities = true; - hasStoppingActivities = true; - } - if (r.app == app) { - app.hasActivities = true; + } else { + if (procState > ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) { + procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; + app.adjType = "cch-act"; + } } } } - if (adj == hiddenAdj && !app.hasActivities) { - if (app.hasClientActivities) { - adj = clientHiddenAdj; - app.adjType = "bg-client-act"; - } else { - // Whoops, this process is completely empty as far as we know - // at this point. - adj = emptyAdj; - app.empty = true; - app.adjType = "bg-empty"; - } - } - if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { if (app.foregroundServices) { // The user is aware of this app, so make it visible. adj = ProcessList.PERCEPTIBLE_APP_ADJ; - app.hidden = false; + procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; + app.cached = false; app.adjType = "fg-service"; schedGroup = Process.THREAD_GROUP_DEFAULT; } else if (app.forcingToForeground != null) { // The user is aware of this app, so make it visible. adj = ProcessList.PERCEPTIBLE_APP_ADJ; - app.hidden = false; + procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; + app.cached = false; app.adjType = "force-fg"; app.adjSource = app.forcingToForeground; schedGroup = Process.THREAD_GROUP_DEFAULT; @@ -13086,32 +14201,46 @@ public final class ActivityManagerService extends ActivityManagerNative interesting = true; } - if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ && app == mHeavyWeightProcess) { - // We don't want to kill the current heavy-weight process. - adj = ProcessList.HEAVY_WEIGHT_APP_ADJ; - schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; - app.hidden = false; - app.adjType = "heavy"; + if (app == mHeavyWeightProcess) { + if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ) { + // We don't want to kill the current heavy-weight process. + adj = ProcessList.HEAVY_WEIGHT_APP_ADJ; + schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; + app.cached = false; + app.adjType = "heavy"; + } + if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) { + procState = ActivityManager.PROCESS_STATE_HEAVY_WEIGHT; + } } - if (adj > ProcessList.HOME_APP_ADJ && app == mHomeProcess) { - // This process is hosting what we currently consider to be the - // home app, so we don't want to let it go into the background. - adj = ProcessList.HOME_APP_ADJ; - schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; - app.hidden = false; - app.adjType = "home"; + if (app == mHomeProcess) { + if (adj > ProcessList.HOME_APP_ADJ) { + // This process is hosting what we currently consider to be the + // home app, so we don't want to let it go into the background. + adj = ProcessList.HOME_APP_ADJ; + schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; + app.cached = false; + app.adjType = "home"; + } + if (procState > ActivityManager.PROCESS_STATE_HOME) { + procState = ActivityManager.PROCESS_STATE_HOME; + } } - if (adj > ProcessList.PREVIOUS_APP_ADJ && app == mPreviousProcess - && app.activities.size() > 0) { - // This was the previous process that showed UI to the user. - // We want to try to keep it around more aggressively, to give - // a good experience around switching between two apps. - adj = ProcessList.PREVIOUS_APP_ADJ; - schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; - app.hidden = false; - app.adjType = "previous"; + if (app == mPreviousProcess && app.activities.size() > 0) { + if (adj > ProcessList.PREVIOUS_APP_ADJ) { + // This was the previous process that showed UI to the user. + // We want to try to keep it around more aggressively, to give + // a good experience around switching between two apps. + adj = ProcessList.PREVIOUS_APP_ADJ; + schedGroup = Process.THREAD_GROUP_BG_NONINTERACTIVE; + app.cached = false; + app.adjType = "previous"; + } + if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) { + procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY; + } } if (false) Slog.i(TAG, "OOM " + app + ": initial adj=" + adj @@ -13122,330 +14251,390 @@ public final class ActivityManagerService extends ActivityManagerNative // this gives us a baseline and makes sure we don't get into an // infinite recursion. app.adjSeq = mAdjSeq; - app.curRawAdj = app.nonStoppingAdj = adj; + app.curRawAdj = adj; + app.hasStartedServices = false; if (mBackupTarget != null && app == mBackupTarget.app) { // If possible we want to avoid killing apps while they're being backed up if (adj > ProcessList.BACKUP_APP_ADJ) { if (DEBUG_BACKUP) Slog.v(TAG, "oom BACKUP_APP_ADJ for " + app); adj = ProcessList.BACKUP_APP_ADJ; + if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) { + procState = ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND; + } app.adjType = "backup"; - app.hidden = false; + app.cached = false; + } + if (procState > ActivityManager.PROCESS_STATE_BACKUP) { + procState = ActivityManager.PROCESS_STATE_BACKUP; } } - if (app.services.size() != 0 && (adj > ProcessList.FOREGROUND_APP_ADJ - || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) { - final long now = SystemClock.uptimeMillis(); - // This process is more important if the top activity is - // bound to the service. - Iterator<ServiceRecord> jt = app.services.iterator(); - while (jt.hasNext() && adj > ProcessList.FOREGROUND_APP_ADJ) { - ServiceRecord s = jt.next(); - if (s.startRequested) { - if (app.hasShownUi && app != mHomeProcess) { - // If this process has shown some UI, let it immediately - // go to the LRU list because it may be pretty heavy with - // UI stuff. We'll tag it with a label just to help - // debug and understand what is going on. - if (adj > ProcessList.SERVICE_ADJ) { - app.adjType = "started-bg-ui-services"; - } - } else { - if (now < (s.lastActivity + ActiveServices.MAX_SERVICE_INACTIVITY)) { - // This service has seen some activity within - // recent memory, so we will keep its process ahead - // of the background processes. - if (adj > ProcessList.SERVICE_ADJ) { - adj = ProcessList.SERVICE_ADJ; - app.adjType = "started-services"; - app.hidden = false; - } - } - // If we have let the service slide into the background - // state, still have some text describing what it is doing - // even though the service no longer has an impact. + boolean mayBeTop = false; + + for (int is = app.services.size()-1; + is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ + || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE + || procState > ActivityManager.PROCESS_STATE_TOP); + is--) { + ServiceRecord s = app.services.valueAt(is); + if (s.startRequested) { + app.hasStartedServices = true; + if (procState > ActivityManager.PROCESS_STATE_SERVICE) { + procState = ActivityManager.PROCESS_STATE_SERVICE; + } + if (app.hasShownUi && app != mHomeProcess) { + // If this process has shown some UI, let it immediately + // go to the LRU list because it may be pretty heavy with + // UI stuff. We'll tag it with a label just to help + // debug and understand what is going on. + if (adj > ProcessList.SERVICE_ADJ) { + app.adjType = "cch-started-ui-services"; + } + } else { + if (now < (s.lastActivity + ActiveServices.MAX_SERVICE_INACTIVITY)) { + // This service has seen some activity within + // recent memory, so we will keep its process ahead + // of the background processes. if (adj > ProcessList.SERVICE_ADJ) { - app.adjType = "started-bg-services"; + adj = ProcessList.SERVICE_ADJ; + app.adjType = "started-services"; + app.cached = false; } } - // Don't kill this process because it is doing work; it - // has said it is doing work. - app.keeping = true; + // If we have let the service slide into the background + // state, still have some text describing what it is doing + // even though the service no longer has an impact. + if (adj > ProcessList.SERVICE_ADJ) { + app.adjType = "cch-started-services"; + } } - if (s.connections.size() > 0 && (adj > ProcessList.FOREGROUND_APP_ADJ - || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) { - Iterator<ArrayList<ConnectionRecord>> kt - = s.connections.values().iterator(); - while (kt.hasNext() && adj > ProcessList.FOREGROUND_APP_ADJ) { - ArrayList<ConnectionRecord> clist = kt.next(); - for (int i=0; i<clist.size() && adj > ProcessList.FOREGROUND_APP_ADJ; i++) { - // XXX should compute this based on the max of - // all connected clients. - ConnectionRecord cr = clist.get(i); - if (cr.binding.client == app) { - // Binding to ourself is not interesting. - continue; - } - if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0) { - ProcessRecord client = cr.binding.client; - int clientAdj = adj; - int myHiddenAdj = hiddenAdj; - if (myHiddenAdj > client.hiddenAdj) { - if (client.hiddenAdj >= ProcessList.VISIBLE_APP_ADJ) { - myHiddenAdj = client.hiddenAdj; - } else { - myHiddenAdj = ProcessList.VISIBLE_APP_ADJ; - } - } - int myClientHiddenAdj = clientHiddenAdj; - if (myClientHiddenAdj > client.clientHiddenAdj) { - if (client.clientHiddenAdj >= ProcessList.VISIBLE_APP_ADJ) { - myClientHiddenAdj = client.clientHiddenAdj; - } else { - myClientHiddenAdj = ProcessList.VISIBLE_APP_ADJ; - } + // Don't kill this process because it is doing work; it + // has said it is doing work. + app.keeping = true; + } + for (int conni = s.connections.size()-1; + conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ + || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE + || procState > ActivityManager.PROCESS_STATE_TOP); + conni--) { + ArrayList<ConnectionRecord> clist = s.connections.valueAt(conni); + for (int i = 0; + i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ + || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE + || procState > ActivityManager.PROCESS_STATE_TOP); + i++) { + // XXX should compute this based on the max of + // all connected clients. + ConnectionRecord cr = clist.get(i); + if (cr.binding.client == app) { + // Binding to ourself is not interesting. + continue; + } + if ((cr.flags&Context.BIND_WAIVE_PRIORITY) == 0) { + ProcessRecord client = cr.binding.client; + int clientAdj = computeOomAdjLocked(client, cachedAdj, + TOP_APP, doingAll, now); + int clientProcState = client.curProcState; + if (clientProcState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) { + // If the other app is cached for any reason, for purposes here + // we are going to consider it empty. The specific cached state + // doesn't propagate except under certain conditions. + clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; + } + String adjType = null; + if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) { + // Not doing bind OOM management, so treat + // this guy more like a started service. + if (app.hasShownUi && app != mHomeProcess) { + // If this process has shown some UI, let it immediately + // go to the LRU list because it may be pretty heavy with + // UI stuff. We'll tag it with a label just to help + // debug and understand what is going on. + if (adj > clientAdj) { + adjType = "cch-bound-ui-services"; } - int myEmptyAdj = emptyAdj; - if (myEmptyAdj > client.emptyAdj) { - if (client.emptyAdj >= ProcessList.VISIBLE_APP_ADJ) { - myEmptyAdj = client.emptyAdj; - } else { - myEmptyAdj = ProcessList.VISIBLE_APP_ADJ; + app.cached = false; + clientAdj = adj; + clientProcState = procState; + } else { + if (now >= (s.lastActivity + + ActiveServices.MAX_SERVICE_INACTIVITY)) { + // This service has not seen activity within + // recent memory, so allow it to drop to the + // LRU list if there is no other reason to keep + // it around. We'll also tag it with a label just + // to help debug and undertand what is going on. + if (adj > clientAdj) { + adjType = "cch-bound-services"; } + clientAdj = adj; } - clientAdj = computeOomAdjLocked(client, myHiddenAdj, - myClientHiddenAdj, myEmptyAdj, TOP_APP, true, doingAll); - String adjType = null; - if ((cr.flags&Context.BIND_ALLOW_OOM_MANAGEMENT) != 0) { - // Not doing bind OOM management, so treat - // this guy more like a started service. - if (app.hasShownUi && app != mHomeProcess) { - // If this process has shown some UI, let it immediately - // go to the LRU list because it may be pretty heavy with - // UI stuff. We'll tag it with a label just to help - // debug and understand what is going on. - if (adj > clientAdj) { - adjType = "bound-bg-ui-services"; - } - app.hidden = false; - clientAdj = adj; - } else { - if (now >= (s.lastActivity - + ActiveServices.MAX_SERVICE_INACTIVITY)) { - // This service has not seen activity within - // recent memory, so allow it to drop to the - // LRU list if there is no other reason to keep - // it around. We'll also tag it with a label just - // to help debug and undertand what is going on. - if (adj > clientAdj) { - adjType = "bound-bg-services"; - } - clientAdj = adj; - } - } - } else if ((cr.flags&Context.BIND_AUTO_CREATE) != 0) { - if ((cr.flags&Context.BIND_NOT_VISIBLE) == 0) { - // If this connection is keeping the service - // created, then we want to try to better follow - // its memory management semantics for activities. - // That is, if it is sitting in the background - // LRU list as a hidden process (with activities), - // we don't want the service it is connected to - // to go into the empty LRU and quickly get killed, - // because I'll we'll do is just end up restarting - // the service. - app.hasClientActivities |= client.hasActivities; + } + } else if ((cr.flags&Context.BIND_AUTO_CREATE) != 0) { + if ((cr.flags&Context.BIND_NOT_VISIBLE) == 0) { + // If this connection is keeping the service + // created, then we want to try to better follow + // its memory management semantics for activities. + // That is, if it is sitting in the background + // LRU list as a cached process (with activities), + // we don't want the service it is connected to + // to go into the empty LRU and quickly get killed, + // because all we'll do is just end up restarting + // the service. + if (client.hasActivities) { + if (procState > + ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT) { + procState = + ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT; + app.adjType = "cch-client-act"; } + app.hasClientActivities = true; } - if (adj > clientAdj) { - // If this process has recently shown UI, and - // the process that is binding to it is less - // important than being visible, then we don't - // care about the binding as much as we care - // about letting this process get into the LRU - // list to be killed and restarted if needed for - // memory. - if (app.hasShownUi && app != mHomeProcess - && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { - adjType = "bound-bg-ui-services"; - } else { - if ((cr.flags&(Context.BIND_ABOVE_CLIENT - |Context.BIND_IMPORTANT)) != 0) { - adj = clientAdj; - } else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0 - && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ - && adj > ProcessList.PERCEPTIBLE_APP_ADJ) { - adj = ProcessList.PERCEPTIBLE_APP_ADJ; - } else if (clientAdj > ProcessList.VISIBLE_APP_ADJ) { - adj = clientAdj; - } else { - app.pendingUiClean = true; - if (adj > ProcessList.VISIBLE_APP_ADJ) { - adj = ProcessList.VISIBLE_APP_ADJ; - } - } - if (!client.hidden) { - app.hidden = false; - } - if (client.keeping) { - app.keeping = true; - } - adjType = "service"; + } + } + if (adj > clientAdj) { + // If this process has recently shown UI, and + // the process that is binding to it is less + // important than being visible, then we don't + // care about the binding as much as we care + // about letting this process get into the LRU + // list to be killed and restarted if needed for + // memory. + if (app.hasShownUi && app != mHomeProcess + && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { + adjType = "cch-bound-ui-services"; + } else { + if ((cr.flags&(Context.BIND_ABOVE_CLIENT + |Context.BIND_IMPORTANT)) != 0) { + adj = clientAdj; + } else if ((cr.flags&Context.BIND_NOT_VISIBLE) != 0 + && clientAdj < ProcessList.PERCEPTIBLE_APP_ADJ + && adj > ProcessList.PERCEPTIBLE_APP_ADJ) { + adj = ProcessList.PERCEPTIBLE_APP_ADJ; + } else if (clientAdj > ProcessList.VISIBLE_APP_ADJ) { + adj = clientAdj; + } else { + if (adj > ProcessList.VISIBLE_APP_ADJ) { + adj = ProcessList.VISIBLE_APP_ADJ; } } - if (adjType != null) { - app.adjType = adjType; - app.adjTypeCode = ActivityManager.RunningAppProcessInfo - .REASON_SERVICE_IN_USE; - app.adjSource = cr.binding.client; - app.adjSourceOom = clientAdj; - app.adjTarget = s.name; + if (!client.cached) { + app.cached = false; } - if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) { - if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) { - schedGroup = Process.THREAD_GROUP_DEFAULT; - } + if (client.keeping) { + app.keeping = true; } + adjType = "service"; } - final ActivityRecord a = cr.activity; - if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) { - if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ && - (a.visible || a.state == ActivityState.RESUMED - || a.state == ActivityState.PAUSING)) { - adj = ProcessList.FOREGROUND_APP_ADJ; - if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) { - schedGroup = Process.THREAD_GROUP_DEFAULT; - } - app.hidden = false; - app.adjType = "service"; - app.adjTypeCode = ActivityManager.RunningAppProcessInfo - .REASON_SERVICE_IN_USE; - app.adjSource = a; - app.adjSourceOom = adj; - app.adjTarget = s.name; + } + if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) { + if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) { + schedGroup = Process.THREAD_GROUP_DEFAULT; + } + if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) { + if (clientProcState == ActivityManager.PROCESS_STATE_TOP) { + // Special handling of clients who are in the top state. + // We *may* want to consider this process to be in the + // top state as well, but only if there is not another + // reason for it to be running. Being on the top is a + // special state, meaning you are specifically running + // for the current top app. If the process is already + // running in the background for some other reason, it + // is more important to continue considering it to be + // in the background state. + mayBeTop = true; + clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; + } else { + // Special handling for above-top states (persistent + // processes). These should not bring the current process + // into the top state, since they are not on top. Instead + // give them the best state after that. + clientProcState = + ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; } } - } - } - } - } - - // Finally, 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.hidden = false; - app.adjType = "bg-services"; - } - } - - if (app.pubProviders.size() != 0 && (adj > ProcessList.FOREGROUND_APP_ADJ - || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) { - Iterator<ContentProviderRecord> jt = app.pubProviders.values().iterator(); - while (jt.hasNext() && (adj > ProcessList.FOREGROUND_APP_ADJ - || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE)) { - ContentProviderRecord cpr = jt.next(); - for (int i = cpr.connections.size()-1; - i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ - || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE); - i--) { - ContentProviderConnection conn = cpr.connections.get(i); - ProcessRecord client = conn.client; - if (client == app) { - // Being our own client is not interesting. - continue; - } - int myHiddenAdj = hiddenAdj; - if (myHiddenAdj > client.hiddenAdj) { - if (client.hiddenAdj > ProcessList.FOREGROUND_APP_ADJ) { - myHiddenAdj = client.hiddenAdj; } else { - myHiddenAdj = ProcessList.FOREGROUND_APP_ADJ; + if (clientProcState < + ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) { + clientProcState = + ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND; + } } - } - int myClientHiddenAdj = clientHiddenAdj; - if (myClientHiddenAdj > client.clientHiddenAdj) { - if (client.clientHiddenAdj >= ProcessList.FOREGROUND_APP_ADJ) { - myClientHiddenAdj = client.clientHiddenAdj; - } else { - myClientHiddenAdj = ProcessList.FOREGROUND_APP_ADJ; + if (procState > clientProcState) { + procState = clientProcState; } - } - int myEmptyAdj = emptyAdj; - if (myEmptyAdj > client.emptyAdj) { - if (client.emptyAdj > ProcessList.FOREGROUND_APP_ADJ) { - myEmptyAdj = client.emptyAdj; - } else { - myEmptyAdj = ProcessList.FOREGROUND_APP_ADJ; - } - } - int clientAdj = computeOomAdjLocked(client, myHiddenAdj, - myClientHiddenAdj, myEmptyAdj, TOP_APP, true, doingAll); - if (adj > clientAdj) { - if (app.hasShownUi && app != mHomeProcess - && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { - app.adjType = "bg-ui-provider"; - } else { - adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ - ? clientAdj : ProcessList.FOREGROUND_APP_ADJ; - app.adjType = "provider"; + if (procState < ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND + && (cr.flags&Context.BIND_SHOWING_UI) != 0) { + app.pendingUiClean = true; } - if (!client.hidden) { - app.hidden = false; + if (adjType != null) { + app.adjType = adjType; + app.adjTypeCode = ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE; + app.adjSource = cr.binding.client; + app.adjSourceOom = clientAdj; + app.adjTarget = s.name; } - if (client.keeping) { - app.keeping = true; + } + final ActivityRecord a = cr.activity; + if ((cr.flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) { + if (a != null && adj > ProcessList.FOREGROUND_APP_ADJ && + (a.visible || a.state == ActivityState.RESUMED + || a.state == ActivityState.PAUSING)) { + adj = ProcessList.FOREGROUND_APP_ADJ; + if ((cr.flags&Context.BIND_NOT_FOREGROUND) == 0) { + schedGroup = Process.THREAD_GROUP_DEFAULT; + } + app.cached = false; + app.adjType = "service"; + app.adjTypeCode = ActivityManager.RunningAppProcessInfo + .REASON_SERVICE_IN_USE; + app.adjSource = a; + app.adjSourceOom = adj; + app.adjTarget = s.name; } - app.adjTypeCode = ActivityManager.RunningAppProcessInfo - .REASON_PROVIDER_IN_USE; - app.adjSource = client; - app.adjSourceOom = clientAdj; - app.adjTarget = cpr.name; - } - if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) { - schedGroup = Process.THREAD_GROUP_DEFAULT; - } - } - // If the provider has external (non-framework) process - // dependencies, ensure that its adjustment is at least - // FOREGROUND_APP_ADJ. - if (cpr.hasExternalProcessHandles()) { - if (adj > ProcessList.FOREGROUND_APP_ADJ) { - adj = ProcessList.FOREGROUND_APP_ADJ; - schedGroup = Process.THREAD_GROUP_DEFAULT; - app.hidden = false; - app.keeping = true; + } + } + } + } + + for (int provi = app.pubProviders.size()-1; + provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ + || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE + || procState > ActivityManager.PROCESS_STATE_TOP); + provi--) { + ContentProviderRecord cpr = app.pubProviders.valueAt(provi); + for (int i = cpr.connections.size()-1; + i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ + || schedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE + || procState > ActivityManager.PROCESS_STATE_TOP); + i--) { + ContentProviderConnection conn = cpr.connections.get(i); + ProcessRecord client = conn.client; + if (client == app) { + // Being our own client is not interesting. + continue; + } + int clientAdj = computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now); + int clientProcState = client.curProcState; + if (clientProcState >= ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) { + // If the other app is cached for any reason, for purposes here + // we are going to consider it empty. + clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; + } + if (adj > clientAdj) { + if (app.hasShownUi && app != mHomeProcess + && clientAdj > ProcessList.PERCEPTIBLE_APP_ADJ) { + app.adjType = "cch-ui-provider"; + } else { + adj = clientAdj > ProcessList.FOREGROUND_APP_ADJ + ? clientAdj : ProcessList.FOREGROUND_APP_ADJ; app.adjType = "provider"; - app.adjTarget = cpr.name; } + app.cached &= client.cached; + app.keeping |= client.keeping; + app.adjTypeCode = ActivityManager.RunningAppProcessInfo + .REASON_PROVIDER_IN_USE; + app.adjSource = client; + app.adjSourceOom = clientAdj; + app.adjTarget = cpr.name; + } + if (clientProcState <= ActivityManager.PROCESS_STATE_TOP) { + if (clientProcState == ActivityManager.PROCESS_STATE_TOP) { + // Special handling of clients who are in the top state. + // We *may* want to consider this process to be in the + // top state as well, but only if there is not another + // reason for it to be running. Being on the top is a + // special state, meaning you are specifically running + // for the current top app. If the process is already + // running in the background for some other reason, it + // is more important to continue considering it to be + // in the background state. + mayBeTop = true; + clientProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; + } else { + // Special handling for above-top states (persistent + // processes). These should not bring the current process + // into the top state, since they are not on top. Instead + // give them the best state after that. + clientProcState = + ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; + } + } + if (procState > clientProcState) { + procState = clientProcState; + } + if (client.curSchedGroup == Process.THREAD_GROUP_DEFAULT) { + schedGroup = Process.THREAD_GROUP_DEFAULT; + } + } + // If the provider has external (non-framework) process + // dependencies, ensure that its adjustment is at least + // FOREGROUND_APP_ADJ. + if (cpr.hasExternalProcessHandles()) { + if (adj > ProcessList.FOREGROUND_APP_ADJ) { + adj = ProcessList.FOREGROUND_APP_ADJ; + schedGroup = Process.THREAD_GROUP_DEFAULT; + app.cached = false; + app.keeping = true; + app.adjType = "provider"; + app.adjTarget = cpr.name; + } + if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND) { + procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; } } } + if (mayBeTop && procState > ActivityManager.PROCESS_STATE_TOP) { + // A client of one of our services or providers is in the top state. We + // *may* want to be in the top state, but not if we are already running in + // the background for some other reason. For the decision here, we are going + // to pick out a few specific states that we want to remain in when a client + // is top (states that tend to be longer-term) and otherwise allow it to go + // to the top state. + switch (procState) { + case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND: + case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND: + case ActivityManager.PROCESS_STATE_SERVICE: + // These all are longer-term states, so pull them up to the top + // of the background states, but not all the way to the top state. + procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND; + break; + default: + // Otherwise, top is a better choice, so take it. + procState = ActivityManager.PROCESS_STATE_TOP; + break; + } + } + if (adj == ProcessList.SERVICE_ADJ) { if (doingAll) { - app.serviceb = mNewNumServiceProcs > (mNumServiceProcs/3); + app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3); mNewNumServiceProcs++; + //Slog.i(TAG, "ADJ " + app + " serviceb=" + app.serviceb); + if (!app.serviceb) { + // This service isn't far enough down on the LRU list to + // normally be a B service, but if we are low on RAM and it + // is large we want to force it down since we would prefer to + // keep launcher over it. + if (mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL + && app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) { + app.serviceHighRam = true; + app.serviceb = true; + //Slog.i(TAG, "ADJ " + app + " high ram!"); + } else { + mNewNumAServiceProcs++; + //Slog.i(TAG, "ADJ " + app + " not high ram!"); + } + } else { + app.serviceHighRam = false; + } } if (app.serviceb) { adj = ProcessList.SERVICE_B_ADJ; } - } else { - app.serviceb = false; - } - - app.nonStoppingAdj = adj; - - if (hasStoppingActivities) { - // Only upgrade adjustment. - if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { - adj = ProcessList.PERCEPTIBLE_APP_ADJ; - app.adjType = "stopping"; - } } app.curRawAdj = adj; @@ -13458,28 +14647,18 @@ public final class ActivityManagerService extends ActivityManagerNative schedGroup = Process.THREAD_GROUP_DEFAULT; } } - if (adj < ProcessList.HIDDEN_APP_MIN_ADJ) { + if (adj < ProcessList.CACHED_APP_MIN_ADJ) { app.keeping = true; } - if (app.hasAboveClient) { - // If this process has bound to any services with BIND_ABOVE_CLIENT, - // then we need to drop its adjustment to be lower than the service's - // in order to honor the request. We want to drop it by one adjustment - // level... but there is special meaning applied to various levels so - // we will skip some of them. - if (adj < ProcessList.FOREGROUND_APP_ADJ) { - // System process will not get dropped, ever - } else if (adj < ProcessList.VISIBLE_APP_ADJ) { - adj = ProcessList.VISIBLE_APP_ADJ; - } else if (adj < ProcessList.PERCEPTIBLE_APP_ADJ) { - adj = ProcessList.PERCEPTIBLE_APP_ADJ; - } else if (adj < ProcessList.HIDDEN_APP_MIN_ADJ) { - adj = ProcessList.HIDDEN_APP_MIN_ADJ; - } else if (adj < ProcessList.HIDDEN_APP_MAX_ADJ) { - adj++; - } - } + // Do final modification to adj. Everything we do between here and applying + // the final setAdj must be done in this function, because we will also use + // it when computing the final cached adj later. Note that we don't need to + // worry about this for max adj above, since max adj will always be used to + // keep it out of the cached vaues. + adj = app.modifyRawOomAdj(adj); + + app.curProcState = procState; int importance = app.memImportance; if (importance == 0 || adj != app.curAdj || schedGroup != app.curSchedGroup) { @@ -13490,7 +14669,7 @@ public final class ActivityManagerService extends ActivityManagerNative // interesting in this process then we will push it to the // background importance. importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND; - } else if (adj >= ProcessList.HIDDEN_APP_MIN_ADJ) { + } else if (adj >= ProcessList.CACHED_APP_MIN_ADJ) { importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_BACKGROUND; } else if (adj >= ProcessList.SERVICE_B_ADJ) { importance = ActivityManager.RunningAppProcessInfo.IMPORTANCE_SERVICE; @@ -13565,6 +14744,47 @@ public final class ActivityManagerService extends ActivityManagerNative } /** + * Schedule PSS collection of a process. + */ + void requestPssLocked(ProcessRecord proc, int procState) { + if (mPendingPssProcesses.contains(proc)) { + return; + } + if (mPendingPssProcesses.size() == 0) { + mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG); + } + if (DEBUG_PSS) Slog.d(TAG, "Requesting PSS of: " + proc); + proc.pssProcState = procState; + mPendingPssProcesses.add(proc); + } + + /** + * Schedule PSS collection of all processes. + */ + void requestPssAllProcsLocked(long now, boolean always, boolean memLowered) { + if (!always) { + if (now < (mLastFullPssTime + + (memLowered ? FULL_PSS_LOWERED_INTERVAL : FULL_PSS_MIN_INTERVAL))) { + return; + } + } + if (DEBUG_PSS) Slog.d(TAG, "Requesting PSS of all procs! memLowered=" + memLowered); + mLastFullPssTime = now; + mPendingPssProcesses.ensureCapacity(mLruProcesses.size()); + mPendingPssProcesses.clear(); + for (int i=mLruProcesses.size()-1; i>=0; i--) { + ProcessRecord app = mLruProcesses.get(i); + if (memLowered || now > (app.lastStateTime+ProcessList.PSS_ALL_INTERVAL)) { + app.pssProcState = app.setProcState; + app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true, + mSleeping, now); + mPendingPssProcesses.add(app); + } + } + mBgHandler.sendEmptyMessage(COLLECT_PSS_BG_MSG); + } + + /** * Ask a given process to GC right now. */ final void performAppGcLocked(ProcessRecord app) { @@ -13594,8 +14814,7 @@ public final class ActivityManagerService extends ActivityManagerNative } } return !processingBroadcasts - && (mSleeping || (mMainStack.mResumedActivity != null && - mMainStack.mResumedActivity.idle)); + && (mSleeping || mStackSupervisor.allResumedActivitiesIdle()); } /** @@ -13771,24 +14990,18 @@ public final class ActivityManagerService extends ActivityManagerNative stats.reportExcessiveWakeLocked(app.info.uid, app.processName, realtimeSince, wtimeUsed); } - Slog.w(TAG, "Excessive wake lock in " + app.processName - + " (pid " + app.pid + "): held " + wtimeUsed + killUnneededProcessLocked(app, "excessive wake held " + wtimeUsed + " during " + realtimeSince); - EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid, - app.processName, app.setAdj, "excessive wake lock"); - Process.killProcessQuiet(app.pid); + app.baseProcessTracker.reportExcessiveWake(app.pkgList); } else if (doCpuKills && uptimeSince > 0 && ((cputimeUsed*100)/uptimeSince) >= 50) { synchronized (stats) { stats.reportExcessiveCpuLocked(app.info.uid, app.processName, uptimeSince, cputimeUsed); } - Slog.w(TAG, "Excessive CPU in " + app.processName - + " (pid " + app.pid + "): used " + cputimeUsed + killUnneededProcessLocked(app, "excessive cpu " + cputimeUsed + " during " + uptimeSince); - EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid, - app.processName, app.setAdj, "excessive cpu"); - Process.killProcessQuiet(app.pid); + app.baseProcessTracker.reportExcessiveCpu(app.pkgList); } else { app.lastWakeTime = wtime; app.lastCpuTime = app.curCpuTime; @@ -13797,22 +15010,10 @@ public final class ActivityManagerService extends ActivityManagerNative } } - private final boolean updateOomAdjLocked(ProcessRecord app, int hiddenAdj, - int clientHiddenAdj, int emptyAdj, ProcessRecord TOP_APP, boolean doingAll) { - app.hiddenAdj = hiddenAdj; - app.clientHiddenAdj = clientHiddenAdj; - app.emptyAdj = emptyAdj; - - if (app.thread == null) { - return false; - } - - final boolean wasKeeping = app.keeping; - + private final boolean applyOomAdjLocked(ProcessRecord app, boolean wasKeeping, + ProcessRecord TOP_APP, boolean doingAll, boolean reportingProcessState, long now) { boolean success = true; - computeOomAdjLocked(app, hiddenAdj, clientHiddenAdj, emptyAdj, TOP_APP, false, doingAll); - if (app.curRawAdj != app.setRawAdj) { if (wasKeeping && !app.keeping) { // This app is no longer something we want to keep. Note @@ -13847,11 +15048,7 @@ public final class ActivityManagerService extends ActivityManagerNative + " to " + app.curSchedGroup); if (app.waitingToKill != null && app.setSchedGroup == Process.THREAD_GROUP_BG_NONINTERACTIVE) { - Slog.i(TAG, "Killing " + app.toShortString() + ": " + app.waitingToKill); - EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid, - app.processName, app.setAdj, app.waitingToKill); - app.killedBackground = true; - Process.killProcessQuiet(app.pid); + killUnneededProcessLocked(app, app.waitingToKill); success = false; } else { if (true) { @@ -13873,37 +15070,105 @@ public final class ActivityManagerService extends ActivityManagerNative } } } + Process.setSwappiness(app.pid, + app.curSchedGroup <= Process.THREAD_GROUP_BG_NONINTERACTIVE); + } + } + if (app.repProcState != app.curProcState) { + app.repProcState = app.curProcState; + if (!reportingProcessState && app.thread != null) { + try { + if (false) { + //RuntimeException h = new RuntimeException("here"); + Slog.i(TAG, "Sending new process state " + app.repProcState + + " to " + app /*, h*/); + } + app.thread.setProcessState(app.repProcState); + } catch (RemoteException e) { + } + } + } + if (app.setProcState < 0 || ProcessList.procStatesDifferForMem(app.curProcState, + app.setProcState)) { + app.lastStateTime = now; + app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, true, + mSleeping, now); + if (DEBUG_PSS) Slog.d(TAG, "Process state change from " + + ProcessList.makeProcStateString(app.setProcState) + " to " + + ProcessList.makeProcStateString(app.curProcState) + " next pss in " + + (app.nextPssTime-now) + ": " + app); + } else { + if (now > app.nextPssTime || (now > (app.lastPssTime+ProcessList.PSS_MAX_INTERVAL) + && now > (app.lastStateTime+ProcessList.PSS_MIN_TIME_FROM_STATE_CHANGE))) { + requestPssLocked(app, app.setProcState); + app.nextPssTime = ProcessList.computeNextPssTime(app.curProcState, false, + mSleeping, now); + } else if (false && DEBUG_PSS) { + Slog.d(TAG, "Not requesting PSS of " + app + ": next=" + (app.nextPssTime-now)); + } + } + if (app.setProcState != app.curProcState) { + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG, + "Proc state change of " + app.processName + + " to " + app.curProcState); + app.setProcState = app.curProcState; + if (app.setProcState >= ActivityManager.PROCESS_STATE_HOME) { + app.notCachedSinceIdle = false; + } + if (!doingAll) { + setProcessTrackerState(app, mProcessStats.getMemFactorLocked(), now); + } else { + app.procStateChanged = true; } } return success; } - private final ActivityRecord resumedAppLocked() { - ActivityRecord resumedActivity = mMainStack.mResumedActivity; - if (resumedActivity == null || resumedActivity.app == null) { - resumedActivity = mMainStack.mPausingActivity; - if (resumedActivity == null || resumedActivity.app == null) { - resumedActivity = mMainStack.topRunningActivityLocked(null); - } + private final void setProcessTrackerState(ProcessRecord proc, int memFactor, long now) { + if (proc.thread != null && proc.baseProcessTracker != null) { + proc.baseProcessTracker.setState(proc.repProcState, memFactor, now, proc.pkgList); + } + } + + private final boolean updateOomAdjLocked(ProcessRecord app, int cachedAdj, + ProcessRecord TOP_APP, boolean doingAll, boolean reportingProcessState, long now) { + if (app.thread == null) { + return false; } - return resumedActivity; + + final boolean wasKeeping = app.keeping; + + computeOomAdjLocked(app, cachedAdj, TOP_APP, doingAll, now); + + return applyOomAdjLocked(app, wasKeeping, TOP_APP, doingAll, + reportingProcessState, now); + } + + private final ActivityRecord resumedAppLocked() { + return mStackSupervisor.resumedAppLocked(); } final boolean updateOomAdjLocked(ProcessRecord app) { + return updateOomAdjLocked(app, false); + } + + final boolean updateOomAdjLocked(ProcessRecord app, boolean doingProcessState) { final ActivityRecord TOP_ACT = resumedAppLocked(); final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null; - int curAdj = app.curAdj; - final boolean wasHidden = curAdj >= ProcessList.HIDDEN_APP_MIN_ADJ - && curAdj <= ProcessList.HIDDEN_APP_MAX_ADJ; + final boolean wasCached = app.cached; mAdjSeq++; - boolean success = updateOomAdjLocked(app, app.hiddenAdj, app.clientHiddenAdj, - app.emptyAdj, TOP_APP, false); - final boolean nowHidden = app.curAdj >= ProcessList.HIDDEN_APP_MIN_ADJ - && app.curAdj <= ProcessList.HIDDEN_APP_MAX_ADJ; - if (nowHidden != wasHidden) { - // Changed to/from hidden state, so apps after it in the LRU + // This is the desired cached adjusment we want to tell it to use. + // If our app is currently cached, we know it, and that is it. Otherwise, + // we don't know it yet, and it needs to now be cached we will then + // need to do a complete oom adj. + final int cachedAdj = app.curRawAdj >= ProcessList.CACHED_APP_MIN_ADJ + ? app.curRawAdj : ProcessList.UNKNOWN_ADJ; + boolean success = updateOomAdjLocked(app, cachedAdj, TOP_APP, false, doingProcessState, + SystemClock.uptimeMillis()); + if (wasCached != app.cached || app.curRawAdj == ProcessList.UNKNOWN_ADJ) { + // Changed to/from cached state, so apps after it in the LRU // list may also be changed. updateOomAdjLocked(); } @@ -13913,7 +15178,9 @@ public final class ActivityManagerService extends ActivityManagerNative final void updateOomAdjLocked() { final ActivityRecord TOP_ACT = resumedAppLocked(); final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null; - final long oldTime = SystemClock.uptimeMillis() - ProcessList.MAX_EMPTY_TIME; + final long now = SystemClock.uptimeMillis(); + final long oldTime = now - ProcessList.MAX_EMPTY_TIME; + final int N = mLruProcesses.size(); if (false) { RuntimeException e = new RuntimeException(); @@ -13923,143 +15190,154 @@ public final class ActivityManagerService extends ActivityManagerNative mAdjSeq++; mNewNumServiceProcs = 0; + mNewNumAServiceProcs = 0; final int emptyProcessLimit; - final int hiddenProcessLimit; + final int cachedProcessLimit; if (mProcessLimit <= 0) { - emptyProcessLimit = hiddenProcessLimit = 0; + emptyProcessLimit = cachedProcessLimit = 0; } else if (mProcessLimit == 1) { emptyProcessLimit = 1; - hiddenProcessLimit = 0; + cachedProcessLimit = 0; } else { - emptyProcessLimit = (mProcessLimit*2)/3; - hiddenProcessLimit = mProcessLimit - emptyProcessLimit; + emptyProcessLimit = ProcessList.computeEmptyProcessLimit(mProcessLimit); + cachedProcessLimit = mProcessLimit - emptyProcessLimit; } // Let's determine how many processes we have running vs. // how many slots we have for background processes; we may want // to put multiple processes in a slot of there are enough of // them. - int numSlots = (ProcessList.HIDDEN_APP_MAX_ADJ - - ProcessList.HIDDEN_APP_MIN_ADJ + 1) / 2; - int numEmptyProcs = mLruProcesses.size()-mNumNonHiddenProcs-mNumHiddenProcs; - if (numEmptyProcs > hiddenProcessLimit) { - // If there are more empty processes than our limit on hidden - // processes, then use the hidden process limit for the factor. + int numSlots = (ProcessList.CACHED_APP_MAX_ADJ + - ProcessList.CACHED_APP_MIN_ADJ + 1) / 2; + int numEmptyProcs = N - mNumNonCachedProcs - mNumCachedHiddenProcs; + if (numEmptyProcs > cachedProcessLimit) { + // If there are more empty processes than our limit on cached + // processes, then use the cached process limit for the factor. // This ensures that the really old empty processes get pushed // down to the bottom, so if we are running low on memory we will - // have a better chance at keeping around more hidden processes + // have a better chance at keeping around more cached processes // instead of a gazillion empty processes. - numEmptyProcs = hiddenProcessLimit; + numEmptyProcs = cachedProcessLimit; } int emptyFactor = numEmptyProcs/numSlots; if (emptyFactor < 1) emptyFactor = 1; - int hiddenFactor = (mNumHiddenProcs > 0 ? mNumHiddenProcs : 1)/numSlots; - if (hiddenFactor < 1) hiddenFactor = 1; - int stepHidden = 0; + int cachedFactor = (mNumCachedHiddenProcs > 0 ? mNumCachedHiddenProcs : 1)/numSlots; + if (cachedFactor < 1) cachedFactor = 1; + int stepCached = 0; int stepEmpty = 0; - int numHidden = 0; + int numCached = 0; int numEmpty = 0; int numTrimming = 0; - mNumNonHiddenProcs = 0; - mNumHiddenProcs = 0; + mNumNonCachedProcs = 0; + mNumCachedHiddenProcs = 0; // First update the OOM adjustment for each of the // application processes based on their current state. - int i = mLruProcesses.size(); - int curHiddenAdj = ProcessList.HIDDEN_APP_MIN_ADJ; - int nextHiddenAdj = curHiddenAdj+1; - int curEmptyAdj = ProcessList.HIDDEN_APP_MIN_ADJ; + int curCachedAdj = ProcessList.CACHED_APP_MIN_ADJ; + int nextCachedAdj = curCachedAdj+1; + int curClientCachedAdj = curCachedAdj+1; + int curEmptyAdj = ProcessList.CACHED_APP_MIN_ADJ; int nextEmptyAdj = curEmptyAdj+2; - int curClientHiddenAdj = curEmptyAdj; - while (i > 0) { - i--; + for (int i=N-1; i>=0; i--) { ProcessRecord app = mLruProcesses.get(i); - //Slog.i(TAG, "OOM " + app + ": cur hidden=" + curHiddenAdj); - updateOomAdjLocked(app, curHiddenAdj, curClientHiddenAdj, curEmptyAdj, TOP_APP, true); - if (!app.killedBackground) { - if (app.curRawAdj == curHiddenAdj && app.hasActivities) { - // This process was assigned as a hidden process... step the - // hidden level. - mNumHiddenProcs++; - if (curHiddenAdj != nextHiddenAdj) { - stepHidden++; - if (stepHidden >= hiddenFactor) { - stepHidden = 0; - curHiddenAdj = nextHiddenAdj; - nextHiddenAdj += 2; - if (nextHiddenAdj > ProcessList.HIDDEN_APP_MAX_ADJ) { - nextHiddenAdj = ProcessList.HIDDEN_APP_MAX_ADJ; - } - if (curClientHiddenAdj <= curHiddenAdj) { - curClientHiddenAdj = curHiddenAdj + 1; - if (curClientHiddenAdj > ProcessList.HIDDEN_APP_MAX_ADJ) { - curClientHiddenAdj = ProcessList.HIDDEN_APP_MAX_ADJ; + if (!app.killedByAm && app.thread != null) { + app.procStateChanged = false; + final boolean wasKeeping = app.keeping; + computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now); + + // If we haven't yet assigned the final cached adj + // to the process, do that now. + if (app.curAdj >= ProcessList.UNKNOWN_ADJ) { + switch (app.curProcState) { + case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: + // This process is a cached process holding activities... + // assign it the next cached value for that type, and then + // step that cached level. + app.curRawAdj = curCachedAdj; + app.curAdj = app.modifyRawOomAdj(curCachedAdj); + if (curCachedAdj != nextCachedAdj) { + stepCached++; + if (stepCached >= cachedFactor) { + stepCached = 0; + curCachedAdj = nextCachedAdj; + nextCachedAdj += 2; + if (nextCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) { + nextCachedAdj = ProcessList.CACHED_APP_MAX_ADJ; + } + if (curClientCachedAdj <= curCachedAdj) { + curClientCachedAdj = curCachedAdj + 1; + if (curClientCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) { + curClientCachedAdj = ProcessList.CACHED_APP_MAX_ADJ; + } + } } } - } - } - numHidden++; - if (numHidden > hiddenProcessLimit) { - Slog.i(TAG, "No longer want " + app.processName - + " (pid " + app.pid + "): hidden #" + numHidden); - EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid, - app.processName, app.setAdj, "too many background"); - app.killedBackground = true; - Process.killProcessQuiet(app.pid); - } - } else if (app.curRawAdj == curHiddenAdj && app.hasClientActivities) { - // This process has a client that has activities. We will have - // given it the current hidden adj; here we will just leave it - // without stepping the hidden adj. - curClientHiddenAdj++; - if (curClientHiddenAdj > ProcessList.HIDDEN_APP_MAX_ADJ) { - curClientHiddenAdj = ProcessList.HIDDEN_APP_MAX_ADJ; - } - } else { - if (app.curRawAdj == curEmptyAdj || app.curRawAdj == curHiddenAdj) { - // This process was assigned as an empty process... step the - // empty level. - if (curEmptyAdj != nextEmptyAdj) { - stepEmpty++; - if (stepEmpty >= emptyFactor) { - stepEmpty = 0; - curEmptyAdj = nextEmptyAdj; - nextEmptyAdj += 2; - if (nextEmptyAdj > ProcessList.HIDDEN_APP_MAX_ADJ) { - nextEmptyAdj = ProcessList.HIDDEN_APP_MAX_ADJ; + break; + case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: + // Special case for cached client processes... just step + // down from after regular cached processes. + app.curRawAdj = curClientCachedAdj; + app.curAdj = app.modifyRawOomAdj(curClientCachedAdj); + curClientCachedAdj++; + if (curClientCachedAdj > ProcessList.CACHED_APP_MAX_ADJ) { + curClientCachedAdj = ProcessList.CACHED_APP_MAX_ADJ; + } + break; + default: + // For everything else, assign next empty cached process + // level and bump that up. Note that this means that + // long-running services that have dropped down to the + // cached level will be treated as empty (since their process + // state is still as a service), which is what we want. + app.curRawAdj = curEmptyAdj; + app.curAdj = app.modifyRawOomAdj(curEmptyAdj); + if (curEmptyAdj != nextEmptyAdj) { + stepEmpty++; + if (stepEmpty >= emptyFactor) { + stepEmpty = 0; + curEmptyAdj = nextEmptyAdj; + nextEmptyAdj += 2; + if (nextEmptyAdj > ProcessList.CACHED_APP_MAX_ADJ) { + nextEmptyAdj = ProcessList.CACHED_APP_MAX_ADJ; + } } } - } - } else if (app.curRawAdj < ProcessList.HIDDEN_APP_MIN_ADJ) { - mNumNonHiddenProcs++; + break; } - if (app.curAdj >= ProcessList.HIDDEN_APP_MIN_ADJ - && !app.hasClientActivities) { + } + + applyOomAdjLocked(app, wasKeeping, TOP_APP, true, false, now); + + // Count the number of process types. + switch (app.curProcState) { + case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: + case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: + mNumCachedHiddenProcs++; + numCached++; + if (numCached > cachedProcessLimit) { + killUnneededProcessLocked(app, "cached #" + numCached); + } + break; + case ActivityManager.PROCESS_STATE_CACHED_EMPTY: if (numEmpty > ProcessList.TRIM_EMPTY_APPS && app.lastActivityTime < oldTime) { - Slog.i(TAG, "No longer want " + app.processName - + " (pid " + app.pid + "): empty for " - + ((oldTime+ProcessList.MAX_EMPTY_TIME-app.lastActivityTime) - / 1000) + "s"); - EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid, - app.processName, app.setAdj, "old background process"); - app.killedBackground = true; - Process.killProcessQuiet(app.pid); + killUnneededProcessLocked(app, "empty for " + + ((oldTime + ProcessList.MAX_EMPTY_TIME - app.lastActivityTime) + / 1000) + "s"); } else { numEmpty++; if (numEmpty > emptyProcessLimit) { - Slog.i(TAG, "No longer want " + app.processName - + " (pid " + app.pid + "): empty #" + numEmpty); - EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid, - app.processName, app.setAdj, "too many background"); - app.killedBackground = true; - Process.killProcessQuiet(app.pid); + killUnneededProcessLocked(app, "empty #" + numEmpty); } } - } + break; + default: + mNumNonCachedProcs++; + break; } + if (app.isolated && app.services.size() <= 0) { // If this is an isolated process, and there are no // services running in it, then the process is no longer @@ -14067,16 +15345,11 @@ public final class ActivityManagerService extends ActivityManagerNative // definition not re-use the same process again, and it is // good to avoid having whatever code was running in them // left sitting around after no longer needed. - Slog.i(TAG, "Isolated process " + app.processName - + " (pid " + app.pid + ") no longer needed"); - EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid, - app.processName, app.setAdj, "isolated not needed"); - app.killedBackground = true; - Process.killProcessQuiet(app.pid); - } - if (app.nonStoppingAdj >= ProcessList.HOME_APP_ADJ - && app.nonStoppingAdj != ProcessList.SERVICE_B_ADJ - && !app.killedBackground) { + killUnneededProcessLocked(app, "isolated not needed"); + } + + if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME + && !app.killedByAm) { numTrimming++; } } @@ -14090,32 +15363,72 @@ public final class ActivityManagerService extends ActivityManagerNative // are managing to keep around is less than half the maximum we desire; // if we are keeping a good number around, we'll let them use whatever // memory they want. - if (numHidden <= ProcessList.TRIM_HIDDEN_APPS + final int numCachedAndEmpty = numCached + numEmpty; + int memFactor; + if (numCached <= ProcessList.TRIM_CACHED_APPS && numEmpty <= ProcessList.TRIM_EMPTY_APPS) { - final int numHiddenAndEmpty = numHidden + numEmpty; - final int N = mLruProcesses.size(); + if (numCachedAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) { + memFactor = ProcessStats.ADJ_MEM_FACTOR_CRITICAL; + } else if (numCachedAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) { + memFactor = ProcessStats.ADJ_MEM_FACTOR_LOW; + } else { + memFactor = ProcessStats.ADJ_MEM_FACTOR_MODERATE; + } + } else { + memFactor = ProcessStats.ADJ_MEM_FACTOR_NORMAL; + } + // We always allow the memory level to go up (better). We only allow it to go + // down if we are in a state where that is allowed, *and* the total number of processes + // has gone down since last time. + if (DEBUG_OOM_ADJ) Slog.d(TAG, "oom: memFactor=" + memFactor + " last=" + mLastMemoryLevel + + " allowLow=" + mAllowLowerMemLevel + " numProcs=" + mLruProcesses.size() + + " last=" + mLastNumProcesses); + if (memFactor > mLastMemoryLevel) { + if (!mAllowLowerMemLevel || mLruProcesses.size() >= mLastNumProcesses) { + memFactor = mLastMemoryLevel; + if (DEBUG_OOM_ADJ) Slog.d(TAG, "Keeping last mem factor!"); + } + } + mLastMemoryLevel = memFactor; + mLastNumProcesses = mLruProcesses.size(); + boolean allChanged = mProcessStats.setMemFactorLocked(memFactor, !mSleeping, now); + final int trackerMemFactor = mProcessStats.getMemFactorLocked(); + if (memFactor != ProcessStats.ADJ_MEM_FACTOR_NORMAL) { + if (mLowRamStartTime == 0) { + mLowRamStartTime = now; + } + int step = 0; + int fgTrimLevel; + switch (memFactor) { + case ProcessStats.ADJ_MEM_FACTOR_CRITICAL: + fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL; + break; + case ProcessStats.ADJ_MEM_FACTOR_LOW: + fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; + break; + default: + fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE; + break; + } int factor = numTrimming/3; int minFactor = 2; if (mHomeProcess != null) minFactor++; if (mPreviousProcess != null) minFactor++; if (factor < minFactor) factor = minFactor; - int step = 0; - int fgTrimLevel; - if (numHiddenAndEmpty <= ProcessList.TRIM_CRITICAL_THRESHOLD) { - fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL; - } else if (numHiddenAndEmpty <= ProcessList.TRIM_LOW_THRESHOLD) { - fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW; - } else { - fgTrimLevel = ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE; - } int curLevel = ComponentCallbacks2.TRIM_MEMORY_COMPLETE; - for (i=0; i<N; i++) { + for (int i=N-1; i>=0; i--) { ProcessRecord app = mLruProcesses.get(i); - if (app.nonStoppingAdj >= ProcessList.HOME_APP_ADJ - && app.nonStoppingAdj != ProcessList.SERVICE_B_ADJ - && !app.killedBackground) { + if (allChanged || app.procStateChanged) { + setProcessTrackerState(app, trackerMemFactor, now); + app.procStateChanged = false; + } + if (app.curProcState >= ActivityManager.PROCESS_STATE_HOME + && !app.killedByAm) { if (app.trimMemoryLevel < curLevel && app.thread != null) { try { + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG, + "Trimming memory of " + app.processName + + " to " + curLevel); app.thread.scheduleTrimMemory(curLevel); } catch (RemoteException e) { } @@ -14129,7 +15442,7 @@ public final class ActivityManagerService extends ActivityManagerNative // be in a consistent state at this point. // For these apps we will also finish their activities // to help them free memory. - mMainStack.scheduleDestroyActivities(app, false, "trim"); + mStackSupervisor.scheduleDestroyAllActivities(app, "trim"); } } } @@ -14146,10 +15459,13 @@ public final class ActivityManagerService extends ActivityManagerNative break; } } - } else if (app.nonStoppingAdj == ProcessList.HEAVY_WEIGHT_APP_ADJ) { + } else if (app.curProcState == ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) { if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_BACKGROUND && app.thread != null) { try { + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG, + "Trimming memory of heavy-weight " + app.processName + + " to " + ComponentCallbacks2.TRIM_MEMORY_BACKGROUND); app.thread.scheduleTrimMemory( ComponentCallbacks2.TRIM_MEMORY_BACKGROUND); } catch (RemoteException e) { @@ -14157,14 +15473,17 @@ public final class ActivityManagerService extends ActivityManagerNative } app.trimMemoryLevel = ComponentCallbacks2.TRIM_MEMORY_BACKGROUND; } else { - if ((app.nonStoppingAdj > ProcessList.VISIBLE_APP_ADJ || app.systemNoUi) - && app.pendingUiClean) { + if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND + || app.systemNoUi) && app.pendingUiClean) { // If this application is now in the background and it // had done UI, then give it the special trim level to // have it free UI resources. final int level = ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN; if (app.trimMemoryLevel < level && app.thread != null) { try { + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG, + "Trimming memory of bg-ui " + app.processName + + " to " + level); app.thread.scheduleTrimMemory(level); } catch (RemoteException e) { } @@ -14173,6 +15492,9 @@ public final class ActivityManagerService extends ActivityManagerNative } if (app.trimMemoryLevel < fgTrimLevel && app.thread != null) { try { + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG, + "Trimming memory of fg " + app.processName + + " to " + fgTrimLevel); app.thread.scheduleTrimMemory(fgTrimLevel); } catch (RemoteException e) { } @@ -14181,14 +15503,24 @@ public final class ActivityManagerService extends ActivityManagerNative } } } else { - final int N = mLruProcesses.size(); - for (i=0; i<N; i++) { + if (mLowRamStartTime != 0) { + mLowRamTimeSinceLastIdle += now - mLowRamStartTime; + mLowRamStartTime = 0; + } + for (int i=N-1; i>=0; i--) { ProcessRecord app = mLruProcesses.get(i); - if ((app.nonStoppingAdj > ProcessList.VISIBLE_APP_ADJ || app.systemNoUi) - && app.pendingUiClean) { + if (allChanged || app.procStateChanged) { + setProcessTrackerState(app, trackerMemFactor, now); + app.procStateChanged = false; + } + if ((app.curProcState >= ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND + || app.systemNoUi) && app.pendingUiClean) { if (app.trimMemoryLevel < ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN && app.thread != null) { try { + if (DEBUG_SWITCH || DEBUG_OOM_ADJ) Slog.v(TAG, + "Trimming memory of ui hidden " + app.processName + + " to " + ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); app.thread.scheduleTrimMemory( ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN); } catch (RemoteException e) { @@ -14203,7 +15535,25 @@ public final class ActivityManagerService extends ActivityManagerNative if (mAlwaysFinishActivities) { // Need to do this on its own message because the stack may not // be in a consistent state at this point. - mMainStack.scheduleDestroyActivities(null, false, "always-finish"); + mStackSupervisor.scheduleDestroyAllActivities(null, "always-finish"); + } + + if (allChanged) { + requestPssAllProcsLocked(now, false, mProcessStats.isMemFactorLowered()); + } + + if (mProcessStats.shouldWriteNowLocked(now)) { + mHandler.post(new Runnable() { + @Override public void run() { + synchronized (ActivityManagerService.this) { + mProcessStats.writeStateAsyncLocked(); + } + } + }); + } + + if (DEBUG_OOM_ADJ) { + Slog.d(TAG, "Did OOM ADJ in " + (SystemClock.uptimeMillis()-now) + "ms"); } } @@ -14225,6 +15575,7 @@ public final class ActivityManagerService extends ActivityManagerNative if (app.pid > 0 && app.pid != MY_PID) { EventLog.writeEvent(EventLogTags.AM_KILL, app.userId, app.pid, app.processName, app.setAdj, "empty"); + app.killedByAm = true; Process.killProcessQuiet(app.pid); } else { try { @@ -14378,7 +15729,7 @@ public final class ActivityManagerService extends ActivityManagerNative } if (proc == null) { - HashMap<String, SparseArray<ProcessRecord>> all + ArrayMap<String, SparseArray<ProcessRecord>> all = mProcessNames.getMap(); SparseArray<ProcessRecord> procs = all.get(process); if (procs != null && procs.size() > 0) { @@ -14503,7 +15854,6 @@ public final class ActivityManagerService extends ActivityManagerNative } mCurrentUserId = userId; - mCurrentUserArray = new int[] { userId }; final Integer userIdInt = Integer.valueOf(userId); mUserLru.remove(userIdInt); mUserLru.add(userIdInt); @@ -14569,9 +15919,11 @@ public final class ActivityManagerService extends ActivityManagerNative } } - boolean haveActivities = mMainStack.switchUserLocked(userId, uss); - if (!haveActivities) { + boolean homeInFront = mStackSupervisor.switchUserLocked(userId, uss); + if (homeInFront) { startHomeActivityLocked(userId); + } else { + mStackSupervisor.resumeTopActivitiesLocked(); } EventLogTags.writeAmSwitchUser(userId); @@ -14730,10 +16082,11 @@ public final class ActivityManagerService extends ActivityManagerNative final int userId = uss.mHandle.getIdentifier(); Intent intent = new Intent(Intent.ACTION_BOOT_COMPLETED, null); intent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + intent.addFlags(Intent.FLAG_RECEIVER_NO_ABORT); broadcastIntentLocked(null, null, intent, null, null, 0, null, null, android.Manifest.permission.RECEIVE_BOOT_COMPLETED, AppOpsManager.OP_NONE, - false, false, MY_PID, Process.SYSTEM_UID, userId); + true, false, MY_PID, Process.SYSTEM_UID, userId); } int num = mUserLru.size(); int i = 0; @@ -14825,6 +16178,7 @@ public final class ActivityManagerService extends ActivityManagerNative final Intent stoppingIntent = new Intent(Intent.ACTION_USER_STOPPING); stoppingIntent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY); stoppingIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId); + stoppingIntent.putExtra(Intent.EXTRA_SHUTDOWN_USERSPACE_ONLY, true); final Intent shutdownIntent = new Intent(Intent.ACTION_SHUTDOWN); // This is the result receiver for the final shutdown broadcast. final IIntentReceiver shutdownReceiver = new IIntentReceiver.Stub() { @@ -14884,7 +16238,7 @@ public final class ActivityManagerService extends ActivityManagerNative // Clean up all state and processes associated with the user. // Kill all the processes for the user. - forceStopUserLocked(userId); + forceStopUserLocked(userId, "finish user"); } } @@ -14895,6 +16249,8 @@ public final class ActivityManagerService extends ActivityManagerNative } catch (RemoteException e) { } } + + mStackSupervisor.removeUserLocked(userId); } @Override diff --git a/services/java/com/android/server/am/ActivityRecord.java b/services/java/com/android/server/am/ActivityRecord.java index aa82be3cc74f..cf686672a2a3 100644 --- a/services/java/com/android/server/am/ActivityRecord.java +++ b/services/java/com/android/server/am/ActivityRecord.java @@ -16,12 +16,14 @@ package com.android.server.am; +import android.os.Trace; +import com.android.internal.R.styleable; import com.android.internal.app.ResolverActivity; import com.android.server.AttributeCache; import com.android.server.am.ActivityStack.ActivityState; -import android.app.Activity; import android.app.ActivityOptions; +import android.app.ResultInfo; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; @@ -54,8 +56,11 @@ import java.util.HashSet; * An entry in the history stack, representing an activity. */ final class ActivityRecord { + static final String TAG = ActivityManagerService.TAG; + static final boolean DEBUG_SAVED_STATE = ActivityStackSupervisor.DEBUG_SAVED_STATE; + final public static String RECENTS_PACKAGE_NAME = "com.android.systemui.recent"; + final ActivityManagerService service; // owner - final ActivityStack stack; // owner final IApplicationToken.Stub appToken; // window manager token final ActivityInfo info; // all about me final int launchedFromUid; // always the uid who started the activity. @@ -69,22 +74,29 @@ final class ActivityRecord { final String processName; // process where this component wants to run final String taskAffinity; // as per ActivityInfo.taskAffinity final boolean stateNotNeeded; // As per ActivityInfo.flags - final boolean fullscreen; // covers the full screen? + boolean fullscreen; // covers the full screen? final boolean noDisplay; // activity is not displayed? final boolean componentSpecified; // did caller specifiy an explicit component? - final boolean isHomeActivity; // do we consider this to be a home activity? + + static final int APPLICATION_ACTIVITY_TYPE = 0; + static final int HOME_ACTIVITY_TYPE = 1; + static final int RECENTS_ACTIVITY_TYPE = 2; + int mActivityType; + final String baseDir; // where activity source (resources etc) located final String resDir; // where public activity source (public resources etc) located final String dataDir; // where activity data should go CharSequence nonLocalizedLabel; // the label information from the package mgr. int labelRes; // the label information from the package mgr. int icon; // resource identifier of activity's icon. + int logo; // resource identifier of activity's logo. int theme; // resource identifier of activity's theme. int realTheme; // actual theme resource we will use, never 0. int windowFlags; // custom window flags for preview window. TaskRecord task; // the task this is in. ThumbnailHolder thumbHolder; // where our thumbnails should go. - long launchTime; // when we starting launching this activity + long displayStartTime; // when we started launching this activity + long fullyDrawnStartTime; // when we started launching this activity long startTime; // last time this activity was started long lastVisibleTime; // last time this activity became visible long cpuTimeAtResume; // the cpu time of host process at the time of resuming activity @@ -95,9 +107,9 @@ final class ActivityRecord { ActivityRecord resultTo; // who started this entry, so will get our reply final String resultWho; // additional identifier for use by resultTo. final int requestCode; // code given by requester (resultTo) - ArrayList results; // pending ActivityResult objs we have received + ArrayList<ResultInfo> results; // pending ActivityResult objs we have received HashSet<WeakReference<PendingIntentRecord>> pendingResults; // all pending intents for this act - ArrayList newIntents; // any pending new intents for single-top mode + ArrayList<Intent> newIntents; // any pending new intents for single-top mode ActivityOptions pendingOptions; // most recently given options HashSet<ConnectionRecord> connections; // All ConnectionRecord we hold UriPermissionOwner uriPermissions; // current special URI access perms. @@ -128,15 +140,16 @@ final class ActivityRecord { long lastLaunchTime; // time of last lauch of this activity String stringName; // for caching of toString(). - + private boolean inHistory; // are we in the history stack? + final ActivityStackSupervisor mStackSupervisor; void dump(PrintWriter pw, String prefix) { final long now = SystemClock.uptimeMillis(); pw.print(prefix); pw.print("packageName="); pw.print(packageName); pw.print(" processName="); pw.println(processName); pw.print(prefix); pw.print("launchedFromUid="); pw.print(launchedFromUid); - pw.print(" launchedFromPackage="); pw.println(launchedFromPackage); + pw.print(" launchedFromPackage="); pw.print(launchedFromPackage); pw.print(" userId="); pw.println(userId); pw.print(prefix); pw.print("app="); pw.println(app); pw.print(prefix); pw.println(intent.toInsecureStringWithClip()); @@ -152,7 +165,7 @@ final class ActivityRecord { pw.print(prefix); pw.print("dataDir="); pw.println(dataDir); pw.print(prefix); pw.print("stateNotNeeded="); pw.print(stateNotNeeded); pw.print(" componentSpecified="); pw.print(componentSpecified); - pw.print(" isHomeActivity="); pw.println(isHomeActivity); + pw.print(" mActivityType="); pw.println(mActivityType); pw.print(prefix); pw.print("compat="); pw.print(compat); pw.print(" labelRes=0x"); pw.print(Integer.toHexString(labelRes)); pw.print(" icon=0x"); pw.print(Integer.toHexString(icon)); @@ -182,7 +195,7 @@ final class ActivityRecord { if (newIntents != null && newIntents.size() > 0) { pw.print(prefix); pw.println("Pending New Intents:"); for (int i=0; i<newIntents.size(); i++) { - Intent intent = (Intent)newIntents.get(i); + Intent intent = newIntents.get(i); pw.print(prefix); pw.print(" - "); if (intent == null) { pw.println("null"); @@ -210,7 +223,7 @@ final class ActivityRecord { if (lastLaunchTime == 0) pw.print("0"); else TimeUtils.formatDuration(lastLaunchTime, now, pw); pw.println(); - pw.print(prefix); pw.print(" haveState="); pw.print(haveState); + pw.print(prefix); pw.print("haveState="); pw.print(haveState); pw.print(" icicle="); pw.println(icicle); pw.print(prefix); pw.print("state="); pw.print(state); pw.print(" stopped="); pw.print(stopped); @@ -228,6 +241,8 @@ final class ActivityRecord { pw.print(prefix); pw.print("frozenBeforeDestroy="); pw.print(frozenBeforeDestroy); pw.print(" thumbnailNeeded="); pw.print(thumbnailNeeded); pw.print(" forceNewConfig="); pw.println(forceNewConfig); + pw.print(prefix); pw.print("mActivityType="); + pw.println(activityTypeToString(mActivityType)); pw.print(prefix); pw.print("thumbHolder: "); pw.print(Integer.toHexString(System.identityHashCode(thumbHolder))); if (thumbHolder != null) { @@ -235,10 +250,10 @@ final class ActivityRecord { pw.print(" desc="); pw.print(thumbHolder.lastDescription); } pw.println(); - if (launchTime != 0 || startTime != 0) { - pw.print(prefix); pw.print("launchTime="); - if (launchTime == 0) pw.print("0"); - else TimeUtils.formatDuration(launchTime, now, pw); + if (displayStartTime != 0 || startTime != 0) { + pw.print(prefix); pw.print("displayStartTime="); + if (displayStartTime == 0) pw.print("0"); + else TimeUtils.formatDuration(displayStartTime, now, pw); pw.print(" startTime="); if (startTime == 0) pw.print("0"); else TimeUtils.formatDuration(startTime, now, pw); @@ -269,36 +284,33 @@ final class ActivityRecord { weakActivity = new WeakReference<ActivityRecord>(activity); } - @Override public void windowsDrawn() throws RemoteException { + @Override public void windowsDrawn() { ActivityRecord activity = weakActivity.get(); if (activity != null) { activity.windowsDrawn(); } } - @Override public void windowsVisible() throws RemoteException { + @Override public void windowsVisible() { ActivityRecord activity = weakActivity.get(); if (activity != null) { activity.windowsVisible(); } } - @Override public void windowsGone() throws RemoteException { + @Override public void windowsGone() { ActivityRecord activity = weakActivity.get(); if (activity != null) { activity.windowsGone(); } } - @Override public boolean keyDispatchingTimedOut() throws RemoteException { + @Override public boolean keyDispatchingTimedOut(String reason) { ActivityRecord activity = weakActivity.get(); - if (activity != null) { - return activity.keyDispatchingTimedOut(); - } - return false; + return activity != null && activity.keyDispatchingTimedOut(reason); } - @Override public long getKeyDispatchingTimeout() throws RemoteException { + @Override public long getKeyDispatchingTimeout() { ActivityRecord activity = weakActivity.get(); if (activity != null) { return activity.getKeyDispatchingTimeout(); @@ -306,6 +318,7 @@ final class ActivityRecord { return 0; } + @Override public String toString() { StringBuilder sb = new StringBuilder(128); sb.append("Token{"); @@ -326,13 +339,16 @@ final class ActivityRecord { } } - ActivityRecord(ActivityManagerService _service, ActivityStack _stack, ProcessRecord _caller, + boolean isNotResolverActivity() { + return !ResolverActivity.class.getName().equals(realActivity.getClassName()); + } + + ActivityRecord(ActivityManagerService _service, ProcessRecord _caller, int _launchedFromUid, String _launchedFromPackage, Intent _intent, String _resolvedType, ActivityInfo aInfo, Configuration _configuration, ActivityRecord _resultTo, String _resultWho, int _reqCode, - boolean _componentSpecified) { + boolean _componentSpecified, ActivityStackSupervisor supervisor) { service = _service; - stack = _stack; appToken = new Token(this); info = aInfo; launchedFromUid = _launchedFromUid; @@ -361,6 +377,7 @@ final class ActivityRecord { thumbnailNeeded = false; idle = false; hasBeenLaunched = false; + mStackSupervisor = supervisor; // This starts out true, since the initial state of an activity // is that we have everything, and we shouldn't never consider it @@ -390,6 +407,7 @@ final class ActivityRecord { labelRes = app.labelRes; } icon = aInfo.getIconResource(); + logo = aInfo.getLogoResource(); theme = aInfo.getThemeResource(); realTheme = theme; if (realTheme == 0) { @@ -413,10 +431,10 @@ final class ActivityRecord { if (intent != null && (aInfo.flags & ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS) != 0) { intent.addFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS); } - + packageName = aInfo.applicationInfo.packageName; launchMode = aInfo.launchMode; - + AttributeCache.Entry ent = AttributeCache.instance().get(packageName, realTheme, com.android.internal.R.styleable.Window, userId); fullscreen = ent != null && !ent.array.getBoolean( @@ -425,29 +443,22 @@ final class ActivityRecord { com.android.internal.R.styleable.Window_windowIsTranslucent, false); noDisplay = ent != null && ent.array.getBoolean( com.android.internal.R.styleable.Window_windowNoDisplay, false); - - if (!_componentSpecified || _launchedFromUid == Process.myUid() - || _launchedFromUid == 0) { - // If we know the system has determined the component, then - // we can consider this to be a home activity... - if (Intent.ACTION_MAIN.equals(_intent.getAction()) && - _intent.hasCategory(Intent.CATEGORY_HOME) && - _intent.getCategories().size() == 1 && - _intent.getData() == null && - _intent.getType() == null && - (intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && - !ResolverActivity.class.getName().equals(realActivity.getClassName())) { - // This sure looks like a home activity! - // Note the last check is so we don't count the resolver - // activity as being home... really, we don't care about - // doing anything special with something that comes from - // the core framework package. - isHomeActivity = true; - } else { - isHomeActivity = false; - } + + if ((!_componentSpecified || _launchedFromUid == Process.myUid() + || _launchedFromUid == 0) && + Intent.ACTION_MAIN.equals(_intent.getAction()) && + _intent.hasCategory(Intent.CATEGORY_HOME) && + _intent.getCategories().size() == 1 && + _intent.getData() == null && + _intent.getType() == null && + (intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && + isNotResolverActivity()) { + // This sure looks like a home activity! + mActivityType = HOME_ACTIVITY_TYPE; + } else if (realActivity.getClassName().contains(RECENTS_PACKAGE_NAME)) { + mActivityType = RECENTS_ACTIVITY_TYPE; } else { - isHomeActivity = false; + mActivityType = APPLICATION_ACTIVITY_TYPE; } immersive = (aInfo.flags & ActivityInfo.FLAG_IMMERSIVE) != 0; @@ -462,12 +473,15 @@ final class ActivityRecord { packageName = null; fullscreen = true; noDisplay = false; - isHomeActivity = false; + mActivityType = APPLICATION_ACTIVITY_TYPE; immersive = false; } } void setTask(TaskRecord newTask, ThumbnailHolder newThumbHolder, boolean isRoot) { + if (task != null && task.removeActivity(this)) { + mStackSupervisor.removeTask(task); + } if (inHistory && !finishing) { if (task != null) { task.numActivities--; @@ -490,6 +504,25 @@ final class ActivityRecord { } } + boolean changeWindowTranslucency(boolean toOpaque) { + if (fullscreen == toOpaque) { + return false; + } + AttributeCache.Entry ent = + AttributeCache.instance().get(packageName, realTheme, styleable.Window, userId); + if (ent == null + || !ent.array.getBoolean(styleable.Window_windowIsTranslucent, false) + || ent.array.getBoolean(styleable.Window_windowIsFloating, false)) { + return false; + } + + // Keep track of the number of fullscreen activities in this task. + task.numFullscreen += toOpaque ? +1 : -1; + + fullscreen = toOpaque; + return true; + } + void putInHistory() { if (!inHistory) { inHistory = true; @@ -504,6 +537,7 @@ final class ActivityRecord { inHistory = false; if (task != null && !finishing) { task.numActivities--; + task = null; } clearOptionsLocked(); } @@ -513,6 +547,18 @@ final class ActivityRecord { return inHistory; } + boolean isHomeActivity() { + return mActivityType == HOME_ACTIVITY_TYPE; + } + + boolean isRecentsActivity() { + return mActivityType == RECENTS_ACTIVITY_TYPE; + } + + boolean isApplicationActivity() { + return mActivityType == APPLICATION_ACTIVITY_TYPE; + } + void makeFinishing() { if (!finishing) { finishing = true; @@ -525,6 +571,11 @@ final class ActivityRecord { } } + boolean isRootActivity() { + final ArrayList<ActivityRecord> activities = task.mActivities; + return activities.size() == 0 || this == activities.get(0); + } + UriPermissionOwner getUriPermissionsLocked() { if (uriPermissions == null) { uriPermissions = new UriPermissionOwner(service, this); @@ -538,7 +589,7 @@ final class ActivityRecord { ActivityResult r = new ActivityResult(from, resultWho, requestCode, resultCode, resultData); if (results == null) { - results = new ArrayList(); + results = new ArrayList<ResultInfo>(); } results.add(r); } @@ -563,17 +614,16 @@ final class ActivityRecord { void addNewIntentLocked(Intent intent) { if (newIntents == null) { - newIntents = new ArrayList(); + newIntents = new ArrayList<Intent>(); } newIntents.add(intent); } - + /** * Deliver a new Intent to an existing activity, so that its onNewIntent() * method will be called at the proper time. */ final void deliverNewIntentLocked(int callingUid, Intent intent) { - boolean sent = false; // The activity now gets access to the data associated with this Intent. service.grantUriPermissionFromIntentLocked(callingUid, packageName, intent, getUriPermissionsLocked()); @@ -582,15 +632,16 @@ final class ActivityRecord { // device is sleeping, then all activities are stopped, so in that // case we will deliver it if this is the current top activity on its // stack. + boolean unsent = true; if ((state == ActivityState.RESUMED || (service.mSleeping - && stack.topRunningActivityLocked(null) == this)) + && task.stack.topRunningActivityLocked(null) == this)) && app != null && app.thread != null) { try { ArrayList<Intent> ar = new ArrayList<Intent>(); intent = new Intent(intent); ar.add(intent); app.thread.scheduleNewIntent(ar, appToken); - sent = true; + unsent = false; } catch (RemoteException e) { Slog.w(ActivityManagerService.TAG, "Exception thrown sending new intent to " + this, e); @@ -599,7 +650,7 @@ final class ActivityRecord { "Exception thrown sending new intent to " + this, e); } } - if (!sent) { + if (unsent) { addNewIntentLocked(new Intent(intent)); } } @@ -701,9 +752,6 @@ final class ActivityRecord { } void updateThumbnail(Bitmap newThumbnail, CharSequence description) { - if ((intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { - // This is a logical break in the task; it repre - } if (thumbHolder != null) { if (newThumbnail != null) { if (ActivityManagerService.DEBUG_THUMBNAILS) Slog.i(ActivityManagerService.TAG, @@ -727,8 +775,8 @@ final class ActivityRecord { boolean continueLaunchTickingLocked() { if (launchTickTime != 0) { - Message msg = stack.mHandler.obtainMessage(ActivityStack.LAUNCH_TICK_MSG); - msg.obj = this; + final ActivityStack stack = task.stack; + Message msg = stack.mHandler.obtainMessage(ActivityStack.LAUNCH_TICK_MSG, this); stack.mHandler.removeMessages(ActivityStack.LAUNCH_TICK_MSG); stack.mHandler.sendMessageDelayed(msg, ActivityStack.LAUNCH_TICK); return true; @@ -738,7 +786,7 @@ final class ActivityRecord { void finishLaunchTickingLocked() { launchTickTime = 0; - stack.mHandler.removeMessages(ActivityStack.LAUNCH_TICK_MSG); + task.stack.mHandler.removeMessages(ActivityStack.LAUNCH_TICK_MSG); } // IApplicationToken @@ -750,50 +798,91 @@ final class ActivityRecord { // so it is best to leave as-is. return app != null && !app.crashing && !app.notResponding; } - + public void startFreezingScreenLocked(ProcessRecord app, int configChanges) { if (mayFreezeScreenLocked(app)) { service.mWindowManager.startAppFreezingScreen(appToken, configChanges); } } - + public void stopFreezingScreenLocked(boolean force) { if (force || frozenBeforeDestroy) { frozenBeforeDestroy = false; service.mWindowManager.stopAppFreezingScreen(appToken, force); } } - + + public void reportFullyDrawnLocked() { + final long curTime = SystemClock.uptimeMillis(); + if (displayStartTime != 0) { + reportLaunchTimeLocked(curTime); + } + if (fullyDrawnStartTime != 0) { + final ActivityStack stack = task.stack; + final long thisTime = curTime - fullyDrawnStartTime; + final long totalTime = stack.mFullyDrawnStartTime != 0 + ? (curTime - stack.mFullyDrawnStartTime) : thisTime; + if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) { + Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0); + EventLog.writeEvent(EventLogTags.AM_ACTIVITY_FULLY_DRAWN_TIME, + userId, System.identityHashCode(this), shortComponentName, + thisTime, totalTime); + StringBuilder sb = service.mStringBuilder; + sb.setLength(0); + sb.append("Fully drawn "); + sb.append(shortComponentName); + sb.append(": "); + TimeUtils.formatDuration(thisTime, sb); + if (thisTime != totalTime) { + sb.append(" (total "); + TimeUtils.formatDuration(totalTime, sb); + sb.append(")"); + } + Log.i(ActivityManagerService.TAG, sb.toString()); + } + if (totalTime > 0) { + service.mUsageStatsService.noteFullyDrawnTime(realActivity, (int) totalTime); + } + fullyDrawnStartTime = 0; + stack.mFullyDrawnStartTime = 0; + } + } + + private void reportLaunchTimeLocked(final long curTime) { + final ActivityStack stack = task.stack; + final long thisTime = curTime - displayStartTime; + final long totalTime = stack.mLaunchStartTime != 0 + ? (curTime - stack.mLaunchStartTime) : thisTime; + if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) { + Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "launching", 0); + EventLog.writeEvent(EventLogTags.AM_ACTIVITY_LAUNCH_TIME, + userId, System.identityHashCode(this), shortComponentName, + thisTime, totalTime); + StringBuilder sb = service.mStringBuilder; + sb.setLength(0); + sb.append("Displayed "); + sb.append(shortComponentName); + sb.append(": "); + TimeUtils.formatDuration(thisTime, sb); + if (thisTime != totalTime) { + sb.append(" (total "); + TimeUtils.formatDuration(totalTime, sb); + sb.append(")"); + } + Log.i(ActivityManagerService.TAG, sb.toString()); + } + mStackSupervisor.reportActivityLaunchedLocked(false, this, thisTime, totalTime); + if (totalTime > 0) { + service.mUsageStatsService.noteLaunchTime(realActivity, (int)totalTime); + } + displayStartTime = 0; + stack.mLaunchStartTime = 0; + } + public void windowsDrawn() { synchronized(service) { - if (launchTime != 0) { - final long curTime = SystemClock.uptimeMillis(); - final long thisTime = curTime - launchTime; - final long totalTime = stack.mInitialStartTime != 0 - ? (curTime - stack.mInitialStartTime) : thisTime; - if (ActivityManagerService.SHOW_ACTIVITY_START_TIME) { - EventLog.writeEvent(EventLogTags.AM_ACTIVITY_LAUNCH_TIME, - userId, System.identityHashCode(this), shortComponentName, - thisTime, totalTime); - StringBuilder sb = service.mStringBuilder; - sb.setLength(0); - sb.append("Displayed "); - sb.append(shortComponentName); - sb.append(": "); - TimeUtils.formatDuration(thisTime, sb); - if (thisTime != totalTime) { - sb.append(" (total "); - TimeUtils.formatDuration(totalTime, sb); - sb.append(")"); - } - Log.i(ActivityManagerService.TAG, sb.toString()); - } - stack.reportActivityLaunchedLocked(false, this, thisTime, totalTime); - if (totalTime > 0) { - service.mUsageStatsService.noteLaunchTime(realActivity, (int)totalTime); - } - launchTime = 0; - stack.mInitialStartTime = 0; + if (displayStartTime != 0) { + reportLaunchTimeLocked(SystemClock.uptimeMillis()); } startTime = 0; finishLaunchTickingLocked(); @@ -802,7 +891,7 @@ final class ActivityRecord { public void windowsVisible() { synchronized(service) { - stack.reportActivityVisibleLocked(this); + mStackSupervisor.reportActivityVisibleLocked(this); if (ActivityManagerService.DEBUG_SWITCH) Log.v( ActivityManagerService.TAG, "windowsVisible(): " + this); if (!nowVisible) { @@ -812,27 +901,24 @@ final class ActivityRecord { // Instead of doing the full stop routine here, let's just // hide any activities we now can, and let them stop when // the normal idle happens. - stack.processStoppingActivitiesLocked(false); + mStackSupervisor.processStoppingActivitiesLocked(false); } else { // If this activity was already idle, then we now need to // make sure we perform the full stop of any activities // that are waiting to do so. This is because we won't // do that while they are still waiting for this one to // become visible. - final int N = stack.mWaitingVisibleActivities.size(); + final int N = mStackSupervisor.mWaitingVisibleActivities.size(); if (N > 0) { for (int i=0; i<N; i++) { - ActivityRecord r = (ActivityRecord) - stack.mWaitingVisibleActivities.get(i); + ActivityRecord r = mStackSupervisor.mWaitingVisibleActivities.get(i); r.waitingVisible = false; if (ActivityManagerService.DEBUG_SWITCH) Log.v( ActivityManagerService.TAG, "Was waiting for visible: " + r); } - stack.mWaitingVisibleActivities.clear(); - Message msg = Message.obtain(); - msg.what = ActivityStack.IDLE_NOW_MSG; - stack.mHandler.sendMessage(msg); + mStackSupervisor.mWaitingVisibleActivities.clear(); + mStackSupervisor.scheduleIdleLocked(); } } service.scheduleAppGcsLocked(); @@ -845,12 +931,13 @@ final class ActivityRecord { ActivityManagerService.TAG, "windowsGone(): " + this); nowVisible = false; } - + private ActivityRecord getWaitingHistoryRecordLocked() { // First find the real culprit... if we are waiting // for another app to start, then we have paused dispatching // for this activity. ActivityRecord r = this; + final ActivityStack stack = task.stack; if (r.waitingVisible) { // Hmmm, who might we be waiting for? r = stack.mResumedActivity; @@ -862,20 +949,20 @@ final class ActivityRecord { r = this; } } - + return r; } - public boolean keyDispatchingTimedOut() { + public boolean keyDispatchingTimedOut(String reason) { ActivityRecord r; ProcessRecord anrApp; synchronized(service) { r = getWaitingHistoryRecordLocked(); anrApp = r != null ? r.app : null; } - return service.inputDispatchingTimedOut(anrApp, r, this, false); + return service.inputDispatchingTimedOut(anrApp, r, this, false, reason); } - + /** Returns the key dispatching timeout for this application token. */ public long getKeyDispatchingTimeout() { synchronized(service) { @@ -889,7 +976,7 @@ final class ActivityRecord { * currently pausing, or is resumed. */ public boolean isInterestingToUserLocked() { - return visible || nowVisible || state == ActivityState.PAUSING || + return visible || nowVisible || state == ActivityState.PAUSING || state == ActivityState.RESUMED; } @@ -900,20 +987,66 @@ final class ActivityRecord { if (app != null && app.thread != null) { try { app.thread.scheduleSleeping(appToken, _sleeping); - if (sleeping && !stack.mGoingToSleepActivities.contains(this)) { - stack.mGoingToSleepActivities.add(this); + if (_sleeping && !mStackSupervisor.mGoingToSleepActivities.contains(this)) { + mStackSupervisor.mGoingToSleepActivities.add(this); } sleeping = _sleeping; } catch (RemoteException e) { - Slog.w(ActivityStack.TAG, "Exception thrown when sleeping: " - + intent.getComponent(), e); + Slog.w(TAG, "Exception thrown when sleeping: " + intent.getComponent(), e); } } } - + + static void activityResumedLocked(IBinder token) { + final ActivityRecord r = ActivityRecord.forToken(token); + if (DEBUG_SAVED_STATE) Slog.i(TAG, "Resumed activity; dropping state of: " + r); + r.icicle = null; + r.haveState = false; + } + + static int getTaskForActivityLocked(IBinder token, boolean onlyRoot) { + final ActivityRecord r = ActivityRecord.forToken(token); + if (r == null) { + return -1; + } + final TaskRecord task = r.task; + switch (task.mActivities.indexOf(r)) { + case -1: return -1; + case 0: return task.taskId; + default: return onlyRoot ? -1 : task.taskId; + } + } + + static ActivityRecord isInStackLocked(IBinder token) { + final ActivityRecord r = ActivityRecord.forToken(token); + if (r != null) { + return r.task.stack.isInStackLocked(token); + } + return null; + } + + static ActivityStack getStackLocked(IBinder token) { + final ActivityRecord r = ActivityRecord.isInStackLocked(token); + if (r != null) { + return r.task.stack; + } + return null; + } + + private String activityTypeToString(int type) { + switch (type) { + case APPLICATION_ACTIVITY_TYPE: return "APPLICATION_ACTIVITY_TYPE"; + case HOME_ACTIVITY_TYPE: return "HOME_ACTIVITY_TYPE"; + case RECENTS_ACTIVITY_TYPE: return "RECENTS_ACTIVITY_TYPE"; + default: return Integer.toString(type); + } + } + + @Override public String toString() { if (stringName != null) { - return stringName; + return stringName + " t" + (task == null ? -1 : task.taskId) + + (finishing ? " f}" : "}"); } StringBuilder sb = new StringBuilder(128); sb.append("ActivityRecord{"); @@ -922,7 +1055,7 @@ final class ActivityRecord { sb.append(userId); sb.append(' '); sb.append(intent.getComponent().flattenToShortString()); - sb.append('}'); - return stringName = sb.toString(); + stringName = sb.toString(); + return toString(); } } diff --git a/services/java/com/android/server/am/ActivityResult.java b/services/java/com/android/server/am/ActivityResult.java index 12eba3414d9b..6d5bdebe4e36 100644 --- a/services/java/com/android/server/am/ActivityResult.java +++ b/services/java/com/android/server/am/ActivityResult.java @@ -23,7 +23,7 @@ import android.os.Bundle; /** * Pending result information to send back to an activity. */ -class ActivityResult extends ResultInfo { +final class ActivityResult extends ResultInfo { final ActivityRecord mFrom; public ActivityResult(ActivityRecord from, String resultWho, diff --git a/services/java/com/android/server/am/ActivityStack.java b/services/java/com/android/server/am/ActivityStack.java index c344023070a0..0397fd566645 100644 --- a/services/java/com/android/server/am/ActivityStack.java +++ b/services/java/com/android/server/am/ActivityStack.java @@ -16,33 +16,48 @@ package com.android.server.am; -import static android.Manifest.permission.START_ANY_ACTIVITY; -import static android.content.pm.PackageManager.PERMISSION_GRANTED; - -import com.android.internal.app.HeavyWeightSwitcherActivity; +import static com.android.server.am.ActivityManagerService.TAG; +import static com.android.server.am.ActivityManagerService.localLOGV; +import static com.android.server.am.ActivityManagerService.DEBUG_CLEANUP; +import static com.android.server.am.ActivityManagerService.DEBUG_CONFIGURATION; +import static com.android.server.am.ActivityManagerService.DEBUG_PAUSE; +import static com.android.server.am.ActivityManagerService.DEBUG_RESULTS; +import static com.android.server.am.ActivityManagerService.DEBUG_STACK; +import static com.android.server.am.ActivityManagerService.DEBUG_SWITCH; +import static com.android.server.am.ActivityManagerService.DEBUG_TASKS; +import static com.android.server.am.ActivityManagerService.DEBUG_TRANSITION; +import static com.android.server.am.ActivityManagerService.DEBUG_USER_LEAVING; +import static com.android.server.am.ActivityManagerService.DEBUG_VISBILITY; +import static com.android.server.am.ActivityManagerService.VALIDATE_TOKENS; + +import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE; +import static com.android.server.am.ActivityStackSupervisor.DEBUG_APP; +import static com.android.server.am.ActivityStackSupervisor.DEBUG_SAVED_STATE; +import static com.android.server.am.ActivityStackSupervisor.DEBUG_STATES; +import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; + +import android.os.Trace; import com.android.internal.os.BatteryStatsImpl; -import com.android.server.am.ActivityManagerService.PendingActivityLaunch; +import com.android.internal.util.Objects; +import com.android.server.Watchdog; +import com.android.server.am.ActivityManagerService.ItemMatcher; import com.android.server.wm.AppTransition; +import com.android.server.wm.TaskGroup; +import com.android.server.wm.WindowManagerService; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityOptions; import android.app.AppGlobals; -import android.app.IActivityManager; -import android.app.IThumbnailRetriever; -import android.app.IApplicationThread; -import android.app.PendingIntent; +import android.app.IActivityController; +import android.app.IThumbnailReceiver; import android.app.ResultInfo; -import android.app.IActivityManager.WaitResult; +import android.app.ActivityManager.RunningTaskInfo; import android.content.ComponentName; import android.content.Context; -import android.content.IIntentSender; import android.content.Intent; -import android.content.IntentSender; import android.content.pm.ActivityInfo; -import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; -import android.content.pm.ResolveInfo; import android.content.res.Configuration; import android.content.res.Resources; import android.graphics.Bitmap; @@ -54,17 +69,15 @@ import android.os.Handler; import android.os.IBinder; import android.os.Looper; import android.os.Message; -import android.os.ParcelFileDescriptor; -import android.os.PowerManager; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; import android.util.EventLog; -import android.util.Log; import android.util.Slog; import android.view.Display; -import java.io.IOException; +import java.io.FileDescriptor; +import java.io.PrintWriter; import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Iterator; @@ -74,28 +87,6 @@ import java.util.List; * State and management of a single stack of activities. */ final class ActivityStack { - static final String TAG = ActivityManagerService.TAG; - static final boolean localLOGV = ActivityManagerService.localLOGV; - static final boolean DEBUG_SWITCH = ActivityManagerService.DEBUG_SWITCH; - static final boolean DEBUG_PAUSE = ActivityManagerService.DEBUG_PAUSE; - static final boolean DEBUG_VISBILITY = ActivityManagerService.DEBUG_VISBILITY; - static final boolean DEBUG_USER_LEAVING = ActivityManagerService.DEBUG_USER_LEAVING; - static final boolean DEBUG_TRANSITION = ActivityManagerService.DEBUG_TRANSITION; - static final boolean DEBUG_RESULTS = ActivityManagerService.DEBUG_RESULTS; - static final boolean DEBUG_CONFIGURATION = ActivityManagerService.DEBUG_CONFIGURATION; - static final boolean DEBUG_TASKS = ActivityManagerService.DEBUG_TASKS; - static final boolean DEBUG_CLEANUP = ActivityManagerService.DEBUG_CLEANUP; - - static final boolean DEBUG_STATES = false; - static final boolean DEBUG_ADD_REMOVE = false; - static final boolean DEBUG_SAVED_STATE = false; - static final boolean DEBUG_APP = false; - - static final boolean VALIDATE_TOKENS = ActivityManagerService.VALIDATE_TOKENS; - - // How long we wait until giving up on the last activity telling us it - // is idle. - static final int IDLE_TIMEOUT = 10*1000; // Ticks during which we check progress while waiting for an app to launch. static final int LAUNCH_TICK = 500; @@ -110,28 +101,29 @@ final class ActivityStack { // from the application in order to get its saved state. static final int STOP_TIMEOUT = 10*1000; - // How long we can hold the sleep wake lock before giving up. - static final int SLEEP_TIMEOUT = 5*1000; - - // How long we can hold the launch wake lock before giving up. - static final int LAUNCH_TIMEOUT = 10*1000; - // How long we wait until giving up on an activity telling us it has // finished destroying itself. static final int DESTROY_TIMEOUT = 10*1000; - + // How long until we reset a task when the user returns to it. Currently // disabled. static final long ACTIVITY_INACTIVE_RESET_TIME = 0; - + // How long between activity launches that we consider safe to not warn // the user about an unexpected activity being launched on top. static final long START_WARN_TIME = 5*1000; - + // Set to false to disable the preview that is shown while a new activity // is being started. static final boolean SHOW_APP_STARTING_PREVIEW = true; - + + // How long to wait for all background Activities to redraw following a call to + // convertToTranslucent(). + static final long TRANSLUCENT_CONVERSION_TIMEOUT = 2000; + + static final boolean SCREENSHOT_FORCE_565 = ActivityManager + .isLowRamDeviceStatic() ? true : false; + enum ActivityState { INITIALIZING, RESUMED, @@ -145,20 +137,20 @@ final class ActivityStack { } final ActivityManagerService mService; - final boolean mMainStack; - + final WindowManagerService mWindowManager; + final Context mContext; - + /** * The back history of all previous (and possibly still - * running) activities. It contains HistoryRecord objects. + * running) activities. It contains #TaskRecord objects. */ - final ArrayList<ActivityRecord> mHistory = new ArrayList<ActivityRecord>(); + private ArrayList<TaskRecord> mTaskHistory = new ArrayList<TaskRecord>(); /** * Used for validating app tokens with window manager. */ - final ArrayList<IBinder> mValidateAppTokens = new ArrayList<IBinder>(); + final ArrayList<TaskGroup> mValidateAppTokens = new ArrayList<TaskGroup>(); /** * List of running activities, sorted by recent usage. @@ -168,71 +160,10 @@ final class ActivityStack { final ArrayList<ActivityRecord> mLRUActivities = new ArrayList<ActivityRecord>(); /** - * List of activities that are waiting for a new activity - * to become visible before completing whatever operation they are - * supposed to do. - */ - final ArrayList<ActivityRecord> mWaitingVisibleActivities - = new ArrayList<ActivityRecord>(); - - /** - * List of activities that are ready to be stopped, but waiting - * for the next activity to settle down before doing so. It contains - * HistoryRecord objects. - */ - final ArrayList<ActivityRecord> mStoppingActivities - = new ArrayList<ActivityRecord>(); - - /** - * List of activities that are in the process of going to sleep. - */ - final ArrayList<ActivityRecord> mGoingToSleepActivities - = new ArrayList<ActivityRecord>(); - - /** * Animations that for the current transition have requested not to * be considered for the transition animation. */ - final ArrayList<ActivityRecord> mNoAnimActivities - = new ArrayList<ActivityRecord>(); - - /** - * List of activities that are ready to be finished, but waiting - * for the previous activity to settle down before doing so. It contains - * HistoryRecord objects. - */ - final ArrayList<ActivityRecord> mFinishingActivities - = new ArrayList<ActivityRecord>(); - - /** - * List of people waiting to find out about the next launched activity. - */ - final ArrayList<IActivityManager.WaitResult> mWaitingActivityLaunched - = new ArrayList<IActivityManager.WaitResult>(); - - /** - * List of people waiting to find out about the next visible activity. - */ - final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible - = new ArrayList<IActivityManager.WaitResult>(); - - final ArrayList<UserStartedState> mStartingUsers - = new ArrayList<UserStartedState>(); - - /** - * Set when the system is going to sleep, until we have - * successfully paused the current activity and released our wake lock. - * At that point the system is allowed to actually sleep. - */ - final PowerManager.WakeLock mGoingToSleep; - - /** - * We don't want to allow the device to go to sleep while in the process - * of launching an activity. This is primarily to allow alarm intent - * receivers to launch an activity and get that to run before the device - * goes back to sleep. - */ - final PowerManager.WakeLock mLaunchingActivity; + final ArrayList<ActivityRecord> mNoAnimActivities = new ArrayList<ActivityRecord>(); /** * When we are in the process of pausing an activity, before starting the @@ -248,40 +179,42 @@ final class ActivityStack { ActivityRecord mLastPausedActivity = null; /** + * Activities that specify No History must be removed once the user navigates away from them. + * If the device goes to sleep with such an activity in the paused state then we save it here + * and finish it later if another activity replaces it on wakeup. + */ + ActivityRecord mLastNoHistoryActivity = null; + + /** * Current activity that is resumed, or null if there is none. */ ActivityRecord mResumedActivity = null; - + /** * This is the last activity that has been started. It is only used to * identify when multiple activities are started at once so that the user * can be warned they may not be in the activity they think they are. */ ActivityRecord mLastStartedActivity = null; - + + // The topmost Activity passed to convertToTranslucent(). When non-null it means we are + // waiting for all Activities in mUndrawnActivitiesBelowTopTranslucent to be removed as they + // are drawn. When the last member of mUndrawnActivitiesBelowTopTranslucent is removed the + // Activity in mTranslucentActivityWaiting is notified via + // Activity.onTranslucentConversionComplete(false). If a timeout occurs prior to the last + // background activity being drawn then the same call will be made with a true value. + ActivityRecord mTranslucentActivityWaiting = null; + ArrayList<ActivityRecord> mUndrawnActivitiesBelowTopTranslucent = + new ArrayList<ActivityRecord>(); + /** * Set when we know we are going to be calling updateConfiguration() * soon, so want to skip intermediate config checks. */ boolean mConfigWillChange; - /** - * Set to indicate whether to issue an onUserLeaving callback when a - * newly launched activity is being brought in front of us. - */ - boolean mUserLeaving = false; - - long mInitialStartTime = 0; - - /** - * Set when we have taken too long waiting to go to sleep. - */ - boolean mSleepTimeout = false; - - /** - * Dismiss the keyguard after the next activity is displayed? - */ - boolean mDismissKeyguardOnNextActivity = false; + long mLaunchStartTime = 0; + long mFullyDrawnStartTime = 0; /** * Save the most recent screenshot for reuse. This keeps Recents from taking two identical @@ -293,18 +226,19 @@ final class ActivityStack { int mThumbnailWidth = -1; int mThumbnailHeight = -1; - private int mCurrentUser; + int mCurrentUser; + + final int mStackId; + + /** Run all ActivityStacks through this */ + final ActivityStackSupervisor mStackSupervisor; - static final int SLEEP_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG; static final int PAUSE_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 1; - static final int IDLE_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 2; - static final int IDLE_NOW_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 3; - static final int LAUNCH_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 4; - static final int DESTROY_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 5; - static final int RESUME_TOP_ACTIVITY_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 6; - static final int LAUNCH_TICK_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 7; - static final int STOP_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 8; - static final int DESTROY_ACTIVITIES_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 9; + static final int DESTROY_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 2; + static final int LAUNCH_TICK_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 3; + static final int STOP_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 4; + static final int DESTROY_ACTIVITIES_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 5; + static final int TRANSLUCENT_TIMEOUT_MSG = ActivityManagerService.FIRST_ACTIVITY_STACK_MSG + 6; static class ScheduleDestroyArgs { final ProcessRecord mOwner; @@ -323,22 +257,13 @@ final class ActivityStack { //public Handler() { // if (localLOGV) Slog.v(TAG, "Handler started!"); //} - public ActivityStackHandler(Looper looper) { + ActivityStackHandler(Looper looper) { super(looper); } @Override public void handleMessage(Message msg) { switch (msg.what) { - case SLEEP_TIMEOUT_MSG: { - synchronized (mService) { - if (mService.isSleeping()) { - Slog.w(TAG, "Sleep timeout! Sleeping now."); - mSleepTimeout = true; - checkReadyForSleepLocked(); - } - } - } break; case PAUSE_TIMEOUT_MSG: { ActivityRecord r = (ActivityRecord)msg.obj; // We don't at this point know if the activity is fullscreen, @@ -346,33 +271,16 @@ final class ActivityStack { Slog.w(TAG, "Activity pause timeout for " + r); synchronized (mService) { if (r.app != null) { - mService.logAppTooSlow(r.app, r.pauseTime, - "pausing " + r); + mService.logAppTooSlow(r.app, r.pauseTime, "pausing " + r); } + activityPausedLocked(r.appToken, true); } - - activityPaused(r != null ? r.appToken : null, true); - } break; - case IDLE_TIMEOUT_MSG: { - if (mService.mDidDexOpt) { - mService.mDidDexOpt = false; - Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); - nmsg.obj = msg.obj; - mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT); - return; - } - // We don't at this point know if the activity is fullscreen, - // so we need to be conservative and assume it isn't. - ActivityRecord r = (ActivityRecord)msg.obj; - Slog.w(TAG, "Activity idle timeout for " + r); - activityIdleInternal(r != null ? r.appToken : null, true, null); } break; case LAUNCH_TICK_MSG: { ActivityRecord r = (ActivityRecord)msg.obj; synchronized (mService) { if (r.continueLaunchTickingLocked()) { - mService.logAppTooSlow(r.app, r.launchTickTime, - "launching " + r); + mService.logAppTooSlow(r.app, r.launchTickTime, "launching " + r); } } } break; @@ -381,29 +289,8 @@ final class ActivityStack { // We don't at this point know if the activity is fullscreen, // so we need to be conservative and assume it isn't. Slog.w(TAG, "Activity destroy timeout for " + r); - activityDestroyed(r != null ? r.appToken : null); - } break; - case IDLE_NOW_MSG: { - ActivityRecord r = (ActivityRecord)msg.obj; - activityIdleInternal(r != null ? r.appToken : null, false, null); - } break; - case LAUNCH_TIMEOUT_MSG: { - if (mService.mDidDexOpt) { - mService.mDidDexOpt = false; - Message nmsg = mHandler.obtainMessage(LAUNCH_TIMEOUT_MSG); - mHandler.sendMessageDelayed(nmsg, LAUNCH_TIMEOUT); - return; - } synchronized (mService) { - if (mLaunchingActivity.isHeld()) { - Slog.w(TAG, "Launch timeout has expired, giving up wake lock!"); - mLaunchingActivity.release(); - } - } - } break; - case RESUME_TOP_ACTIVITY_MSG: { - synchronized (mService) { - resumeTopActivityLocked(null); + activityDestroyedLocked(r != null ? r.appToken : null); } } break; case STOP_TIMEOUT_MSG: { @@ -422,48 +309,59 @@ final class ActivityStack { synchronized (mService) { destroyActivitiesLocked(args.mOwner, args.mOomAdj, args.mReason); } - } + } break; + case TRANSLUCENT_TIMEOUT_MSG: { + synchronized (mService) { + notifyActivityDrawnLocked(null); + } + } break; } } } - ActivityStack(ActivityManagerService service, Context context, boolean mainStack, Looper looper) { + private int numActivities() { + int count = 0; + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + count += mTaskHistory.get(taskNdx).mActivities.size(); + } + return count; + } + + ActivityStack(ActivityManagerService service, Context context, Looper looper, int stackId) { mHandler = new ActivityStackHandler(looper); mService = service; + mWindowManager = service.mWindowManager; + mStackSupervisor = service.mStackSupervisor; mContext = context; - mMainStack = mainStack; - PowerManager pm = - (PowerManager)context.getSystemService(Context.POWER_SERVICE); - mGoingToSleep = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep"); - mLaunchingActivity = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Launch"); - mLaunchingActivity.setReferenceCounted(false); + mStackId = stackId; + mCurrentUser = service.mCurrentUserId; } - private boolean okToShow(ActivityRecord r) { + boolean okToShow(ActivityRecord r) { return r.userId == mCurrentUser || (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0; } final ActivityRecord topRunningActivityLocked(ActivityRecord notTop) { - int i = mHistory.size()-1; - while (i >= 0) { - ActivityRecord r = mHistory.get(i); - if (!r.finishing && r != notTop && okToShow(r)) { + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + ActivityRecord r = mTaskHistory.get(taskNdx).topRunningActivityLocked(notTop); + if (r != null) { return r; } - i--; } return null; } final ActivityRecord topRunningNonDelayedActivityLocked(ActivityRecord notTop) { - int i = mHistory.size()-1; - while (i >= 0) { - ActivityRecord r = mHistory.get(i); - if (!r.finishing && !r.delayedResume && r != notTop && okToShow(r)) { - return r; + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final TaskRecord task = mTaskHistory.get(taskNdx); + final ArrayList<ActivityRecord> activities = task.mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + ActivityRecord r = activities.get(activityNdx); + if (!r.finishing && !r.delayedResume && r != notTop && okToShow(r)) { + return r; + } } - i--; } return null; } @@ -471,88 +369,154 @@ final class ActivityStack { /** * This is a simplified version of topRunningActivityLocked that provides a number of * optional skip-over modes. It is intended for use with the ActivityController hook only. - * + * * @param token If non-null, any history records matching this token will be skipped. * @param taskId If non-zero, we'll attempt to skip over records with the same task ID. - * + * * @return Returns the HistoryRecord of the next activity on the stack. */ final ActivityRecord topRunningActivityLocked(IBinder token, int taskId) { - int i = mHistory.size()-1; - while (i >= 0) { - ActivityRecord r = mHistory.get(i); - // Note: the taskId check depends on real taskId fields being non-zero - if (!r.finishing && (token != r.appToken) && (taskId != r.task.taskId) - && okToShow(r)) { - return r; + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + TaskRecord task = mTaskHistory.get(taskNdx); + if (task.taskId == taskId) { + continue; + } + ArrayList<ActivityRecord> activities = task.mActivities; + for (int i = activities.size() - 1; i >= 0; --i) { + final ActivityRecord r = activities.get(i); + // Note: the taskId check depends on real taskId fields being non-zero + if (!r.finishing && (token != r.appToken) && okToShow(r)) { + return r; + } } - i--; } return null; } - final int indexOfTokenLocked(IBinder token) { - return mHistory.indexOf(ActivityRecord.forToken(token)); + final ActivityRecord topActivity() { + // Iterate to find the first non-empty task stack. Note that this code can + // be simplified once we stop storing tasks with empty mActivities lists. + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + return activities.get(activityNdx); + } + } + return null; } - final int indexOfActivityLocked(ActivityRecord r) { - return mHistory.indexOf(r); + final TaskRecord topTask() { + final int size = mTaskHistory.size(); + if (size > 0) { + return mTaskHistory.get(size - 1); + } + return null; } - final ActivityRecord isInStackLocked(IBinder token) { - ActivityRecord r = ActivityRecord.forToken(token); - if (mHistory.contains(r)) { - return r; + TaskRecord taskForIdLocked(int id) { + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final TaskRecord task = mTaskHistory.get(taskNdx); + if (task.taskId == id) { + return task; + } + } + return null; + } + + ActivityRecord isInStackLocked(IBinder token) { + final ActivityRecord r = ActivityRecord.forToken(token); + if (r != null) { + final TaskRecord task = r.task; + if (task.mActivities.contains(r) && mTaskHistory.contains(task)) { + if (task.stack != this) Slog.w(TAG, + "Illegal state! task does not point to stack it is in."); + return r; + } } return null; } - private final boolean updateLRUListLocked(ActivityRecord r) { + boolean containsApp(ProcessRecord app) { + if (app == null) { + return false; + } + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if (r.finishing) { + continue; + } + if (r.app == app) { + return true; + } + } + } + return false; + } + + final boolean updateLRUListLocked(ActivityRecord r) { final boolean hadit = mLRUActivities.remove(r); mLRUActivities.add(r); return hadit; } + final boolean isHomeStack() { + return mStackId == HOME_STACK_ID; + } + /** * Returns the top activity in any existing task matching the given * Intent. Returns null if no such task is found. */ - private ActivityRecord findTaskLocked(Intent intent, ActivityInfo info) { + ActivityRecord findTaskLocked(ActivityRecord target) { + Intent intent = target.intent; + ActivityInfo info = target.info; ComponentName cls = intent.getComponent(); if (info.targetActivity != null) { cls = new ComponentName(info.packageName, info.targetActivity); } + final int userId = UserHandle.getUserId(info.applicationInfo.uid); - TaskRecord cp = null; + if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + target + " in " + this); + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final TaskRecord task = mTaskHistory.get(taskNdx); + if (task.userId != userId) { + // Looking for a different task. + if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": different user"); + continue; + } + final ActivityRecord r = task.getTopActivity(); + if (r == null || r.finishing || r.userId != userId || + r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + if (DEBUG_TASKS) Slog.d(TAG, "Skipping " + task + ": mismatch root " + r); + continue; + } - final int userId = UserHandle.getUserId(info.applicationInfo.uid); - final int N = mHistory.size(); - for (int i=(N-1); i>=0; i--) { - ActivityRecord r = mHistory.get(i); - if (!r.finishing && r.task != cp && r.userId == userId - && r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE) { - cp = r.task; - //Slog.i(TAG, "Comparing existing cls=" + r.task.intent.getComponent().flattenToShortString() - // + "/aff=" + r.task.affinity + " to new cls=" - // + intent.getComponent().flattenToShortString() + "/aff=" + taskAffinity); - if (r.task.affinity != null) { - if (r.task.affinity.equals(info.taskAffinity)) { - //Slog.i(TAG, "Found matching affinity!"); - return r; - } - } else if (r.task.intent != null - && r.task.intent.getComponent().equals(cls)) { - //Slog.i(TAG, "Found matching class!"); - //dump(); - //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); - return r; - } else if (r.task.affinityIntent != null - && r.task.affinityIntent.getComponent().equals(cls)) { - //Slog.i(TAG, "Found matching class!"); - //dump(); - //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); + if (DEBUG_TASKS) Slog.d(TAG, "Comparing existing cls=" + + r.task.intent.getComponent().flattenToShortString() + + "/aff=" + r.task.affinity + " to new cls=" + + intent.getComponent().flattenToShortString() + "/aff=" + info.taskAffinity); + if (task.affinity != null) { + if (task.affinity.equals(info.taskAffinity)) { + if (DEBUG_TASKS) Slog.d(TAG, "Found matching affinity!"); return r; } + } else if (task.intent != null && task.intent.getComponent().equals(cls)) { + if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!"); + //dump(); + if (DEBUG_TASKS) Slog.d(TAG, "For Intent " + intent + " bringing to top: " + + r.intent); + return r; + } else if (task.affinityIntent != null + && task.affinityIntent.getComponent().equals(cls)) { + if (DEBUG_TASKS) Slog.d(TAG, "Found matching class!"); + //dump(); + if (DEBUG_TASKS) Slog.d(TAG, "For Intent " + intent + " bringing to top: " + + r.intent); + return r; + } else if (DEBUG_TASKS) { + Slog.d(TAG, "Not a match: " + task); } } @@ -564,18 +528,22 @@ final class ActivityStack { * is the same as the given activity. Returns null if no such activity * is found. */ - private ActivityRecord findActivityLocked(Intent intent, ActivityInfo info) { + ActivityRecord findActivityLocked(Intent intent, ActivityInfo info) { ComponentName cls = intent.getComponent(); if (info.targetActivity != null) { cls = new ComponentName(info.packageName, info.targetActivity); } final int userId = UserHandle.getUserId(info.applicationInfo.uid); - final int N = mHistory.size(); - for (int i=(N-1); i>=0; i--) { - ActivityRecord r = mHistory.get(i); - if (!r.finishing) { - if (r.intent.getComponent().equals(cls) && r.userId == userId) { + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + TaskRecord task = mTaskHistory.get(taskNdx); + if (task.userId != mCurrentUser) { + return null; + } + final ArrayList<ActivityRecord> activities = task.mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + ActivityRecord r = activities.get(activityNdx); + if (!r.finishing && r.intent.getComponent().equals(cls) && r.userId == userId) { //Slog.i(TAG, "Found matching class!"); //dump(); //Slog.i(TAG, "For Intent " + intent + " bringing to top: " + r.intent); @@ -587,343 +555,127 @@ final class ActivityStack { return null; } - final void showAskCompatModeDialogLocked(ActivityRecord r) { - Message msg = Message.obtain(); - msg.what = ActivityManagerService.SHOW_COMPAT_MODE_DIALOG_MSG; - msg.obj = r.task.askedCompatMode ? null : r; - mService.mHandler.sendMessage(msg); - } - /* * Move the activities around in the stack to bring a user to the foreground. - * @return whether there are any activities for the specified user. */ - final boolean switchUserLocked(int userId, UserStartedState uss) { + final void switchUserLocked(int userId) { + if (mCurrentUser == userId) { + return; + } mCurrentUser = userId; - mStartingUsers.add(uss); - - // Only one activity? Nothing to do... - if (mHistory.size() < 2) - return false; - boolean haveActivities = false; - // Check if the top activity is from the new user. - ActivityRecord top = mHistory.get(mHistory.size() - 1); - if (top.userId == userId) return true; - // Otherwise, move the user's activities to the top. - int N = mHistory.size(); - int i = 0; - while (i < N) { - ActivityRecord r = mHistory.get(i); - if (r.userId == userId) { - ActivityRecord moveToTop = mHistory.remove(i); - mHistory.add(moveToTop); - // No need to check the top one now - N--; - haveActivities = true; - } else { - i++; + // Move userId's tasks to the top. + int index = mTaskHistory.size(); + for (int i = 0; i < index; ++i) { + TaskRecord task = mTaskHistory.get(i); + if (task.userId == userId) { + if (DEBUG_TASKS) Slog.d(TAG, "switchUserLocked: stack=" + getStackId() + + " moving " + task + " to top"); + mTaskHistory.remove(i); + mTaskHistory.add(task); + --index; } } - // Transition from the old top to the new top - resumeTopActivityLocked(top); - return haveActivities; + if (VALIDATE_TOKENS) { + validateAppTokensLocked(); + } } - final boolean realStartActivityLocked(ActivityRecord r, - ProcessRecord app, boolean andResume, boolean checkConfig) - throws RemoteException { - - r.startFreezingScreenLocked(app, 0); - mService.mWindowManager.setAppVisibility(r.appToken, true); - - // schedule launch ticks to collect information about slow apps. - r.startLaunchTickingLocked(); + void minimalResumeActivityLocked(ActivityRecord r) { + r.state = ActivityState.RESUMED; + if (DEBUG_STATES) Slog.v(TAG, "Moving to RESUMED: " + r + + " (starting new instance)"); + r.stopped = false; + mResumedActivity = r; + r.task.touchActiveTime(); + mService.addRecentTaskLocked(r.task); + completeResumeLocked(r); + mStackSupervisor.checkReadyForSleepLocked(); + setLaunchTime(r); + if (DEBUG_SAVED_STATE) Slog.i(TAG, "Launch completed; removing icicle of " + r.icicle); + } - // Have the window manager re-evaluate the orientation of - // the screen based on the new activity order. Note that - // as a result of this, it can call back into the activity - // manager with a new orientation. We don't care about that, - // because the activity is not currently running so we are - // just restarting it anyway. - if (checkConfig) { - Configuration config = mService.mWindowManager.updateOrientationFromAppTokens( - mService.mConfiguration, - r.mayFreezeScreenLocked(app) ? r.appToken : null); - mService.updateConfigurationLocked(config, r, false, false); + private void startLaunchTraces() { + if (mFullyDrawnStartTime != 0) { + Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0); } + Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "launching", 0); + Trace.asyncTraceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0); + } - r.app = app; - app.waitingToKill = null; - r.launchCount++; - r.lastLaunchTime = SystemClock.uptimeMillis(); - - if (localLOGV) Slog.v(TAG, "Launching: " + r); - - int idx = app.activities.indexOf(r); - if (idx < 0) { - app.activities.add(r); + private void stopFullyDrawnTraceIfNeeded() { + if (mFullyDrawnStartTime != 0 && mLaunchStartTime == 0) { + Trace.asyncTraceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER, "drawing", 0); + mFullyDrawnStartTime = 0; } - mService.updateLruProcessLocked(app, true); + } - try { - if (app.thread == null) { - throw new RemoteException(); - } - List<ResultInfo> results = null; - List<Intent> newIntents = null; - if (andResume) { - results = r.results; - newIntents = r.newIntents; - } - if (DEBUG_SWITCH) Slog.v(TAG, "Launching: " + r - + " icicle=" + r.icicle - + " with results=" + results + " newIntents=" + newIntents - + " andResume=" + andResume); - if (andResume) { - EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY, - r.userId, System.identityHashCode(r), - r.task.taskId, r.shortComponentName); - } - if (r.isHomeActivity) { - mService.mHomeProcess = app; - } - mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName()); - r.sleeping = false; - r.forceNewConfig = false; - showAskCompatModeDialogLocked(r); - r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo); - String profileFile = null; - ParcelFileDescriptor profileFd = null; - boolean profileAutoStop = false; - if (mService.mProfileApp != null && mService.mProfileApp.equals(app.processName)) { - if (mService.mProfileProc == null || mService.mProfileProc == app) { - mService.mProfileProc = app; - profileFile = mService.mProfileFile; - profileFd = mService.mProfileFd; - profileAutoStop = mService.mAutoStopProfiler; - } - } - app.hasShownUi = true; - app.pendingUiClean = true; - if (profileFd != null) { - try { - profileFd = profileFd.dup(); - } catch (IOException e) { - profileFd = null; - } - } - app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken, - System.identityHashCode(r), r.info, - new Configuration(mService.mConfiguration), - r.compat, r.icicle, results, newIntents, !andResume, - mService.isNextTransitionForward(), profileFile, profileFd, - profileAutoStop); - - if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { - // This may be a heavy-weight process! Note that the package - // manager will ensure that only activity can run in the main - // process of the .apk, which is the only thing that will be - // considered heavy-weight. - if (app.processName.equals(app.info.packageName)) { - if (mService.mHeavyWeightProcess != null - && mService.mHeavyWeightProcess != app) { - Log.w(TAG, "Starting new heavy weight process " + app - + " when already running " - + mService.mHeavyWeightProcess); - } - mService.mHeavyWeightProcess = app; - Message msg = mService.mHandler.obtainMessage( - ActivityManagerService.POST_HEAVY_NOTIFICATION_MSG); - msg.obj = r; - mService.mHandler.sendMessage(msg); - } + void setLaunchTime(ActivityRecord r) { + if (r.displayStartTime == 0) { + r.fullyDrawnStartTime = r.displayStartTime = SystemClock.uptimeMillis(); + if (mLaunchStartTime == 0) { + startLaunchTraces(); + mLaunchStartTime = mFullyDrawnStartTime = r.displayStartTime; } - - } catch (RemoteException e) { - if (r.launchFailed) { - // This is the second time we failed -- finish activity - // and give up. - Slog.e(TAG, "Second failure launching " - + r.intent.getComponent().flattenToShortString() - + ", giving up", e); - mService.appDiedLocked(app, app.pid, app.thread); - requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null, - "2nd-crash", false); - return false; - } - - // This is the first time we failed -- restart process and - // retry. - app.activities.remove(r); - throw e; - } - - r.launchFailed = false; - if (updateLRUListLocked(r)) { - Slog.w(TAG, "Activity " + r - + " being launched, but already in LRU list"); + } else if (mLaunchStartTime == 0) { + startLaunchTraces(); + mLaunchStartTime = mFullyDrawnStartTime = SystemClock.uptimeMillis(); } + } - if (andResume) { - // As part of the process of launching, ActivityThread also performs - // a resume. - r.state = ActivityState.RESUMED; - if (DEBUG_STATES) Slog.v(TAG, "Moving to RESUMED: " + r - + " (starting new instance)"); - r.stopped = false; - mResumedActivity = r; - r.task.touchActiveTime(); - if (mMainStack) { - mService.addRecentTaskLocked(r.task); - } - completeResumeLocked(r); - checkReadyForSleepLocked(); - if (DEBUG_SAVED_STATE) Slog.i(TAG, "Launch completed; removing icicle of " + r.icicle); + void clearLaunchTime(ActivityRecord r) { + // Make sure that there is no activity waiting for this to launch. + if (mStackSupervisor.mWaitingActivityLaunched.isEmpty()) { + r.displayStartTime = r.fullyDrawnStartTime = 0; } else { - // This activity is not starting in the resumed state... which - // should look like we asked it to pause+stop (but remain visible), - // and it has done so and reported back the current icicle and - // other state. - if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r - + " (starting in stopped state)"); - r.state = ActivityState.STOPPED; - r.stopped = true; - } - - // Launch the new version setup screen if needed. We do this -after- - // launching the initial activity (that is, home), so that it can have - // a chance to initialize itself while in the background, making the - // switch back to it faster and look better. - if (mMainStack) { - mService.startSetupActivityLocked(); + mStackSupervisor.removeTimeoutsForActivityLocked(r); + mStackSupervisor.scheduleIdleTimeoutLocked(r); } - - return true; } - private final void startSpecificActivityLocked(ActivityRecord r, - boolean andResume, boolean checkConfig) { - // Is this activity's application already running? - ProcessRecord app = mService.getProcessRecordLocked(r.processName, - r.info.applicationInfo.uid); - - if (r.launchTime == 0) { - r.launchTime = SystemClock.uptimeMillis(); - if (mInitialStartTime == 0) { - mInitialStartTime = r.launchTime; - } - } else if (mInitialStartTime == 0) { - mInitialStartTime = SystemClock.uptimeMillis(); - } - - if (app != null && app.thread != null) { - try { - app.addPackage(r.info.packageName); - realStartActivityLocked(r, app, andResume, checkConfig); - return; - } catch (RemoteException e) { - Slog.w(TAG, "Exception when starting activity " - + r.intent.getComponent().flattenToShortString(), e); - } - - // If a dead object exception was thrown -- fall through to - // restart the application. - } - - mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0, - "activity", r.intent.getComponent(), false, false); - } - - void stopIfSleepingLocked() { - if (mService.isSleeping()) { - if (!mGoingToSleep.isHeld()) { - mGoingToSleep.acquire(); - if (mLaunchingActivity.isHeld()) { - mLaunchingActivity.release(); - mService.mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); - } + void awakeFromSleepingLocked() { + // Ensure activities are no longer sleeping. + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + activities.get(activityNdx).setSleeping(false); } - mHandler.removeMessages(SLEEP_TIMEOUT_MSG); - Message msg = mHandler.obtainMessage(SLEEP_TIMEOUT_MSG); - mHandler.sendMessageDelayed(msg, SLEEP_TIMEOUT); - checkReadyForSleepLocked(); } } - void awakeFromSleepingLocked() { - mHandler.removeMessages(SLEEP_TIMEOUT_MSG); - mSleepTimeout = false; - if (mGoingToSleep.isHeld()) { - mGoingToSleep.release(); + /** + * @return true if something must be done before going to sleep. + */ + boolean checkReadyForSleepLocked() { + if (mResumedActivity != null) { + // Still have something resumed; can't sleep until it is paused. + if (DEBUG_PAUSE) Slog.v(TAG, "Sleep needs to pause " + mResumedActivity); + if (DEBUG_USER_LEAVING) Slog.v(TAG, "Sleep => pause with userLeaving=false"); + startPausingLocked(false, true); + return true; } - // Ensure activities are no longer sleeping. - for (int i=mHistory.size()-1; i>=0; i--) { - ActivityRecord r = mHistory.get(i); - r.setSleeping(false); + if (mPausingActivity != null) { + // Still waiting for something to pause; can't sleep yet. + if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still waiting to pause " + mPausingActivity); + return true; } - mGoingToSleepActivities.clear(); - } - void activitySleptLocked(ActivityRecord r) { - mGoingToSleepActivities.remove(r); - checkReadyForSleepLocked(); + return false; } - void checkReadyForSleepLocked() { - if (!mService.isSleeping()) { - // Do not care. - return; - } - - if (!mSleepTimeout) { - if (mResumedActivity != null) { - // Still have something resumed; can't sleep until it is paused. - if (DEBUG_PAUSE) Slog.v(TAG, "Sleep needs to pause " + mResumedActivity); - if (DEBUG_USER_LEAVING) Slog.v(TAG, "Sleep => pause with userLeaving=false"); - startPausingLocked(false, true); - return; - } - if (mPausingActivity != null) { - // Still waiting for something to pause; can't sleep yet. - if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still waiting to pause " + mPausingActivity); - return; - } - - if (mStoppingActivities.size() > 0) { - // Still need to tell some activities to stop; can't sleep yet. - if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to stop " - + mStoppingActivities.size() + " activities"); - scheduleIdleLocked(); - return; - } - - ensureActivitiesVisibleLocked(null, 0); + void goToSleep() { + ensureActivitiesVisibleLocked(null, 0); - // Make sure any stopped but visible activities are now sleeping. - // This ensures that the activity's onStop() is called. - for (int i=mHistory.size()-1; i>=0; i--) { - ActivityRecord r = mHistory.get(i); + // Make sure any stopped but visible activities are now sleeping. + // This ensures that the activity's onStop() is called. + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); if (r.state == ActivityState.STOPPING || r.state == ActivityState.STOPPED) { r.setSleeping(true); } } - - if (mGoingToSleepActivities.size() > 0) { - // Still need to tell some activities to sleep; can't sleep yet. - if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to sleep " - + mGoingToSleepActivities.size() + " activities"); - return; - } - } - - mHandler.removeMessages(SLEEP_TIMEOUT_MSG); - - if (mGoingToSleep.isHeld()) { - mGoingToSleep.release(); - } - if (mService.mShuttingDown) { - mService.notifyAll(); } } @@ -931,7 +683,15 @@ final class ActivityStack { if (who.noDisplay) { return null; } - + + TaskRecord tr = who.task; + if (mService.getMostRecentTask() != tr && tr.intent != null && + (tr.intent.getFlags() & Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS) != 0) { + // If this task is being excluded from recents, we don't want to take + // the expense of capturing a thumbnail, since we will never show it. + return null; + } + Resources res = mService.mContext.getResources(); int w = mThumbnailWidth; int h = mThumbnailHeight; @@ -944,30 +704,30 @@ final class ActivityStack { if (w > 0) { if (who != mLastScreenshotActivity || mLastScreenshotBitmap == null + || mLastScreenshotActivity.state == ActivityState.RESUMED || mLastScreenshotBitmap.getWidth() != w || mLastScreenshotBitmap.getHeight() != h) { mLastScreenshotActivity = who; - mLastScreenshotBitmap = mService.mWindowManager.screenshotApplications( - who.appToken, Display.DEFAULT_DISPLAY, w, h); + mLastScreenshotBitmap = mWindowManager.screenshotApplications( + who.appToken, Display.DEFAULT_DISPLAY, w, h, SCREENSHOT_FORCE_565); } if (mLastScreenshotBitmap != null) { - return mLastScreenshotBitmap.copy(Config.ARGB_8888, true); + return mLastScreenshotBitmap.copy(mLastScreenshotBitmap.getConfig(), true); } } return null; } - private final void startPausingLocked(boolean userLeaving, boolean uiSleeping) { + final void startPausingLocked(boolean userLeaving, boolean uiSleeping) { if (mPausingActivity != null) { - RuntimeException e = new RuntimeException(); Slog.e(TAG, "Trying to pause when pause is already pending for " - + mPausingActivity, e); + + mPausingActivity, new RuntimeException("here").fillInStackTrace()); } ActivityRecord prev = mResumedActivity; if (prev == null) { - RuntimeException e = new RuntimeException(); - Slog.e(TAG, "Trying to pause when nothing is resumed", e); - resumeTopActivityLocked(null); + Slog.e(TAG, "Trying to pause when nothing is resumed", + new RuntimeException("here").fillInStackTrace()); + mStackSupervisor.resumeTopActivitiesLocked(); return; } if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSING: " + prev); @@ -975,46 +735,44 @@ final class ActivityStack { mResumedActivity = null; mPausingActivity = prev; mLastPausedActivity = prev; + mLastNoHistoryActivity = (prev.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 + || (prev.info.flags & ActivityInfo.FLAG_NO_HISTORY) != 0 ? prev : null; prev.state = ActivityState.PAUSING; prev.task.touchActiveTime(); + clearLaunchTime(prev); prev.updateThumbnail(screenshotActivities(prev), null); + stopFullyDrawnTraceIfNeeded(); mService.updateCpuStats(); - + if (prev.app != null && prev.app.thread != null) { if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending pause: " + prev); try { EventLog.writeEvent(EventLogTags.AM_PAUSE_ACTIVITY, prev.userId, System.identityHashCode(prev), prev.shortComponentName); + mService.updateUsageStats(prev, false); prev.app.thread.schedulePauseActivity(prev.appToken, prev.finishing, userLeaving, prev.configChangeFlags); - if (mMainStack) { - mService.updateUsageStats(prev, false); - } } catch (Exception e) { // Ignore exception, if process died other code will cleanup. Slog.w(TAG, "Exception thrown during pause", e); mPausingActivity = null; mLastPausedActivity = null; + mLastNoHistoryActivity = null; } } else { mPausingActivity = null; mLastPausedActivity = null; + mLastNoHistoryActivity = null; } // If we are not going to sleep, we want to ensure the device is // awake until the next activity is started. - if (!mService.mSleeping && !mService.mShuttingDown) { - mLaunchingActivity.acquire(); - if (!mHandler.hasMessages(LAUNCH_TIMEOUT_MSG)) { - // To be safe, don't allow the wake lock to be held for too long. - Message msg = mHandler.obtainMessage(LAUNCH_TIMEOUT_MSG); - mHandler.sendMessageDelayed(msg, LAUNCH_TIMEOUT); - } + if (!mService.isSleepingOrShuttingDown()) { + mStackSupervisor.acquireLaunchWakelock(); } - if (mPausingActivity != null) { // Have the window manager pause its key dispatching until the new // activity has started. If we're pausing the activity just because @@ -1038,46 +796,27 @@ final class ActivityStack { // This activity failed to schedule the // pause, so just treat it as being paused now. if (DEBUG_PAUSE) Slog.v(TAG, "Activity not running, resuming next."); - resumeTopActivityLocked(null); - } - } - - final void activityResumed(IBinder token) { - ActivityRecord r = null; - - synchronized (mService) { - int index = indexOfTokenLocked(token); - if (index >= 0) { - r = mHistory.get(index); - if (DEBUG_SAVED_STATE) Slog.i(TAG, "Resumed activity; dropping state of: " + r); - r.icicle = null; - r.haveState = false; - } + mStackSupervisor.getFocusedStack().resumeTopActivityLocked(null); } } - final void activityPaused(IBinder token, boolean timeout) { + final void activityPausedLocked(IBinder token, boolean timeout) { if (DEBUG_PAUSE) Slog.v( TAG, "Activity paused: token=" + token + ", timeout=" + timeout); - ActivityRecord r = null; - - synchronized (mService) { - int index = indexOfTokenLocked(token); - if (index >= 0) { - r = mHistory.get(index); - mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); - if (mPausingActivity == r) { - if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSED: " + r - + (timeout ? " (due to timeout)" : " (pause complete)")); - r.state = ActivityState.PAUSED; - completePauseLocked(); - } else { - EventLog.writeEvent(EventLogTags.AM_FAILED_TO_PAUSE, - r.userId, System.identityHashCode(r), r.shortComponentName, - mPausingActivity != null - ? mPausingActivity.shortComponentName : "(none)"); - } + final ActivityRecord r = isInStackLocked(token); + if (r != null) { + mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); + if (mPausingActivity == r) { + if (DEBUG_STATES) Slog.v(TAG, "Moving to PAUSED: " + r + + (timeout ? " (due to timeout)" : " (pause complete)")); + r.state = ActivityState.PAUSED; + completePauseLocked(); + } else { + EventLog.writeEvent(EventLogTags.AM_FAILED_TO_PAUSE, + r.userId, System.identityHashCode(r), r.shortComponentName, + mPausingActivity != null + ? mPausingActivity.shortComponentName : "(none)"); } } } @@ -1108,32 +847,18 @@ final class ActivityStack { } else { if (r.configDestroy) { destroyActivityLocked(r, true, false, "stop-config"); - resumeTopActivityLocked(null); + mStackSupervisor.resumeTopActivitiesLocked(); } else { - // Now that this process has stopped, we may want to consider - // it to be the previous app to try to keep around in case - // the user wants to return to it. - ProcessRecord fgApp = null; - if (mResumedActivity != null) { - fgApp = mResumedActivity.app; - } else if (mPausingActivity != null) { - fgApp = mPausingActivity.app; - } - if (r.app != null && fgApp != null && r.app != fgApp - && r.lastVisibleTime > mService.mPreviousProcessVisibleTime - && r.app != mService.mHomeProcess) { - mService.mPreviousProcess = r.app; - mService.mPreviousProcessVisibleTime = r.lastVisibleTime; - } + mStackSupervisor.updatePreviousProcessLocked(r); } } } } - private final void completePauseLocked() { + private void completePauseLocked() { ActivityRecord prev = mPausingActivity; if (DEBUG_PAUSE) Slog.v(TAG, "Complete pause: " + prev); - + if (prev != null) { if (prev.finishing) { if (DEBUG_PAUSE) Slog.v(TAG, "Executing finish of activity: " + prev); @@ -1142,7 +867,7 @@ final class ActivityStack { if (DEBUG_PAUSE) Slog.v(TAG, "Enqueueing pending stop: " + prev); if (prev.waitingVisible) { prev.waitingVisible = false; - mWaitingVisibleActivities.remove(prev); + mStackSupervisor.mWaitingVisibleActivities.remove(prev); if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v( TAG, "Complete pause, no longer waiting: " + prev); } @@ -1155,15 +880,17 @@ final class ActivityStack { if (DEBUG_PAUSE) Slog.v(TAG, "Destroying after pause: " + prev); destroyActivityLocked(prev, true, false, "pause-config"); } else { - mStoppingActivities.add(prev); - if (mStoppingActivities.size() > 3) { + mStackSupervisor.mStoppingActivities.add(prev); + if (mStackSupervisor.mStoppingActivities.size() > 3 || + prev.frontOfTask && mTaskHistory.size() <= 1) { // If we already have a few activities waiting to stop, // then give up on things going idle and start clearing - // them out. + // them out. Or if r is the last of activity of the last task the stack + // will be empty and must be cleared immediately. if (DEBUG_PAUSE) Slog.v(TAG, "To many pending stops, forcing idle"); - scheduleIdleLocked(); + mStackSupervisor.scheduleIdleLocked(); } else { - checkReadyForSleepLocked(); + mStackSupervisor.checkReadyForSleepLocked(); } } } else { @@ -1173,45 +900,46 @@ final class ActivityStack { mPausingActivity = null; } - if (!mService.isSleeping()) { - resumeTopActivityLocked(prev); + final ActivityStack topStack = mStackSupervisor.getFocusedStack(); + if (!mService.isSleepingOrShuttingDown()) { + mStackSupervisor.resumeTopActivitiesLocked(topStack, prev, null); } else { - checkReadyForSleepLocked(); - ActivityRecord top = topRunningActivityLocked(null); + mStackSupervisor.checkReadyForSleepLocked(); + ActivityRecord top = topStack.topRunningActivityLocked(null); if (top == null || (prev != null && top != prev)) { // If there are no more activities available to run, // do resume anyway to start something. Also if the top // activity on the stack is not the just paused activity, // we need to go ahead and resume it to ensure we complete // an in-flight app switch. - resumeTopActivityLocked(null); + mStackSupervisor.resumeTopActivitiesLocked(topStack, null, null); } } - + if (prev != null) { prev.resumeKeyDispatchingLocked(); - } - if (prev.app != null && prev.cpuTimeAtResume > 0 - && mService.mBatteryStatsService.isOnBattery()) { - long diff = 0; - synchronized (mService.mProcessStatsThread) { - diff = mService.mProcessStats.getCpuTimeForPid(prev.app.pid) - - prev.cpuTimeAtResume; - } - if (diff > 0) { - BatteryStatsImpl bsi = mService.mBatteryStatsService.getActiveStatistics(); - synchronized (bsi) { - BatteryStatsImpl.Uid.Proc ps = - bsi.getProcessStatsLocked(prev.info.applicationInfo.uid, - prev.info.packageName); - if (ps != null) { - ps.addForegroundTimeLocked(diff); + if (prev.app != null && prev.cpuTimeAtResume > 0 + && mService.mBatteryStatsService.isOnBattery()) { + long diff; + synchronized (mService.mProcessCpuThread) { + diff = mService.mProcessCpuTracker.getCpuTimeForPid(prev.app.pid) + - prev.cpuTimeAtResume; + } + if (diff > 0) { + BatteryStatsImpl bsi = mService.mBatteryStatsService.getActiveStatistics(); + synchronized (bsi) { + BatteryStatsImpl.Uid.Proc ps = + bsi.getProcessStatsLocked(prev.info.applicationInfo.uid, + prev.info.packageName); + if (ps != null) { + ps.addForegroundTimeLocked(diff); + } } } } + prev.cpuTimeAtResume = 0; // reset it } - prev.cpuTimeAtResume = 0; // reset it } /** @@ -1219,44 +947,29 @@ final class ActivityStack { * the resumed state (either by launching it or explicitly telling it), * this function updates the rest of our state to match that fact. */ - private final void completeResumeLocked(ActivityRecord next) { + private void completeResumeLocked(ActivityRecord next) { next.idle = false; next.results = null; next.newIntents = null; + if (next.nowVisible) { + // We won't get a call to reportActivityVisibleLocked() so dismiss lockscreen now. + mStackSupervisor.dismissKeyguard(); + } // schedule an idle timeout in case the app doesn't do it for us. - Message msg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); - msg.obj = next; - mHandler.sendMessageDelayed(msg, IDLE_TIMEOUT); + mStackSupervisor.scheduleIdleTimeoutLocked(next); - if (false) { - // The activity was never told to pause, so just keep - // things going as-is. To maintain our own state, - // we need to emulate it coming back and saying it is - // idle. - msg = mHandler.obtainMessage(IDLE_NOW_MSG); - msg.obj = next; - mHandler.sendMessage(msg); - } - - if (mMainStack) { - mService.reportResumedActivityLocked(next); - } + mStackSupervisor.reportResumedActivityLocked(next); - if (mMainStack) { - mService.setFocusedActivityLocked(next); - } next.resumeKeyDispatchingLocked(); - ensureActivitiesVisibleLocked(null, 0); - mService.mWindowManager.executeAppTransition(); mNoAnimActivities.clear(); // Mark the point when the activity is resuming // TODO: To be more accurate, the mark should be before the onCreate, // not after the onResume. But for subsequent starts, onResume is fine. if (next.app != null) { - synchronized (mService.mProcessStatsThread) { - next.cpuTimeAtResume = mService.mProcessStats.getCpuTimeForPid(next.app.pid); + synchronized (mService.mProcessCpuThread) { + next.cpuTimeAtResume = mService.mProcessCpuTracker.getCpuTimeForPid(next.app.pid); } } else { next.cpuTimeAtResume = 0; // Couldn't get the cpu time of process @@ -1264,129 +977,185 @@ final class ActivityStack { } /** + * Version of ensureActivitiesVisible that can easily be called anywhere. + */ + final boolean ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) { + return ensureActivitiesVisibleLocked(starting, configChanges, false); + } + + final boolean ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges, + boolean forceHomeShown) { + ActivityRecord r = topRunningActivityLocked(null); + return r != null && + ensureActivitiesVisibleLocked(r, starting, null, configChanges, forceHomeShown); + } + + /** * Make sure that all activities that need to be visible (that is, they * currently can be seen by the user) actually are. */ - final void ensureActivitiesVisibleLocked(ActivityRecord top, - ActivityRecord starting, String onlyThisProcess, int configChanges) { + final boolean ensureActivitiesVisibleLocked(ActivityRecord top, ActivityRecord starting, + String onlyThisProcess, int configChanges, boolean forceHomeShown) { if (DEBUG_VISBILITY) Slog.v( TAG, "ensureActivitiesVisible behind " + top + " configChanges=0x" + Integer.toHexString(configChanges)); + if (mTranslucentActivityWaiting != top) { + mUndrawnActivitiesBelowTopTranslucent.clear(); + if (mTranslucentActivityWaiting != null) { + // Call the callback with a timeout indication. + notifyActivityDrawnLocked(null); + mTranslucentActivityWaiting = null; + } + mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG); + } + // If the top activity is not fullscreen, then we need to // make sure any activities under it are now visible. - final int count = mHistory.size(); - int i = count-1; - while (mHistory.get(i) != top) { - i--; - } - ActivityRecord r; - boolean behindFullscreen = false; - for (; i>=0; i--) { - r = mHistory.get(i); - if (DEBUG_VISBILITY) Slog.v( - TAG, "Make visible? " + r + " finishing=" + r.finishing - + " state=" + r.state); - if (r.finishing) { - continue; - } - - final boolean doThisProcess = onlyThisProcess == null - || onlyThisProcess.equals(r.processName); - - // First: if this is not the current activity being started, make - // sure it matches the current configuration. - if (r != starting && doThisProcess) { - ensureActivityConfigurationLocked(r, 0); - } - - if (r.app == null || r.app.thread == null) { - if (onlyThisProcess == null - || onlyThisProcess.equals(r.processName)) { - // This activity needs to be visible, but isn't even - // running... get it started, but don't resume it - // at this point. - if (DEBUG_VISBILITY) Slog.v( - TAG, "Start and freeze screen for " + r); - if (r != starting) { - r.startFreezingScreenLocked(r.app, configChanges); - } - if (!r.visible) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Starting and making visible: " + r); - mService.mWindowManager.setAppVisibility(r.appToken, true); - } - if (r != starting) { - startSpecificActivityLocked(r, false, false); - } + boolean aboveTop = true; + boolean showHomeBehindStack = false; + boolean behindFullscreen = !mStackSupervisor.isFrontStack(this) && + !(forceHomeShown && isHomeStack()); + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final TaskRecord task = mTaskHistory.get(taskNdx); + final ArrayList<ActivityRecord> activities = task.mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if (r.finishing) { + continue; } - - } else if (r.visible) { - // If this activity is already visible, then there is nothing - // else to do here. - if (DEBUG_VISBILITY) Slog.v( - TAG, "Skipping: already visible at " + r); - r.stopFreezingScreenLocked(false); - - } else if (onlyThisProcess == null) { - // This activity is not currently visible, but is running. - // Tell it to become visible. - r.visible = true; - if (r.state != ActivityState.RESUMED && r != starting) { - // If this activity is paused, tell it - // to now show its window. + if (aboveTop && r != top) { + continue; + } + aboveTop = false; + if (!behindFullscreen) { if (DEBUG_VISBILITY) Slog.v( - TAG, "Making visible and scheduling visibility: " + r); - try { - mService.mWindowManager.setAppVisibility(r.appToken, true); - r.sleeping = false; - r.app.pendingUiClean = true; - r.app.thread.scheduleWindowVisibility(r.appToken, true); - r.stopFreezingScreenLocked(false); - } catch (Exception e) { - // Just skip on any failure; we'll make it - // visible when it next restarts. - Slog.w(TAG, "Exception thrown making visibile: " - + r.intent.getComponent(), e); + TAG, "Make visible? " + r + " finishing=" + r.finishing + + " state=" + r.state); + + final boolean doThisProcess = onlyThisProcess == null + || onlyThisProcess.equals(r.processName); + + // First: if this is not the current activity being started, make + // sure it matches the current configuration. + if (r != starting && doThisProcess) { + ensureActivityConfigurationLocked(r, 0); } - } - } - // Aggregate current change flags. - configChanges |= r.configChangeFlags; + if (r.app == null || r.app.thread == null) { + if (onlyThisProcess == null || onlyThisProcess.equals(r.processName)) { + // This activity needs to be visible, but isn't even + // running... get it started, but don't resume it + // at this point. + if (DEBUG_VISBILITY) Slog.v(TAG, "Start and freeze screen for " + r); + if (r != starting) { + r.startFreezingScreenLocked(r.app, configChanges); + } + if (!r.visible) { + if (DEBUG_VISBILITY) Slog.v( + TAG, "Starting and making visible: " + r); + mWindowManager.setAppVisibility(r.appToken, true); + } + if (r != starting) { + mStackSupervisor.startSpecificActivityLocked(r, false, false); + } + } - if (r.fullscreen) { - // At this point, nothing else needs to be shown - if (DEBUG_VISBILITY) Slog.v( - TAG, "Stopping: fullscreen at " + r); - behindFullscreen = true; - i--; - break; - } - } + } else if (r.visible) { + // If this activity is already visible, then there is nothing + // else to do here. + if (DEBUG_VISBILITY) Slog.v(TAG, "Skipping: already visible at " + r); + r.stopFreezingScreenLocked(false); - // Now for any activities that aren't visible to the user, make - // sure they no longer are keeping the screen frozen. - while (i >= 0) { - r = mHistory.get(i); - if (DEBUG_VISBILITY) Slog.v( - TAG, "Make invisible? " + r + " finishing=" + r.finishing - + " state=" + r.state - + " behindFullscreen=" + behindFullscreen); - if (!r.finishing) { - if (behindFullscreen) { + } else if (onlyThisProcess == null) { + // This activity is not currently visible, but is running. + // Tell it to become visible. + r.visible = true; + if (r.state != ActivityState.RESUMED && r != starting) { + // If this activity is paused, tell it + // to now show its window. + if (DEBUG_VISBILITY) Slog.v( + TAG, "Making visible and scheduling visibility: " + r); + try { + if (mTranslucentActivityWaiting != null) { + mUndrawnActivitiesBelowTopTranslucent.add(r); + } + mWindowManager.setAppVisibility(r.appToken, true); + r.sleeping = false; + r.app.pendingUiClean = true; + r.app.thread.scheduleWindowVisibility(r.appToken, true); + r.stopFreezingScreenLocked(false); + } catch (Exception e) { + // Just skip on any failure; we'll make it + // visible when it next restarts. + Slog.w(TAG, "Exception thrown making visibile: " + + r.intent.getComponent(), e); + } + } + } + + // Aggregate current change flags. + configChanges |= r.configChangeFlags; + + if (r.fullscreen) { + // At this point, nothing else needs to be shown + if (DEBUG_VISBILITY) Slog.v(TAG, "Fullscreen: at " + r); + behindFullscreen = true; + } else if (task.mOnTopOfHome) { + // Work our way down from r to bottom of task and see if there are any + // visible activities below r. + int rIndex = task.mActivities.indexOf(r); + for ( --rIndex; rIndex >= 0; --rIndex) { + final ActivityRecord blocker = task.mActivities.get(rIndex); + if (!blocker.finishing) { + if (DEBUG_VISBILITY) Slog.v(TAG, "Home visibility for " + + r + " blocked by " + blocker); + break; + } + } + if (rIndex < 0) { + // Got to task bottom without finding a visible activity, show home. + if (DEBUG_VISBILITY) Slog.v(TAG, "Showing home: at " + r); + showHomeBehindStack = true; + behindFullscreen = true; + } + } + } else { + if (DEBUG_VISBILITY) Slog.v( + TAG, "Make invisible? " + r + " finishing=" + r.finishing + + " state=" + r.state + + " behindFullscreen=" + behindFullscreen); + // Now for any activities that aren't visible to the user, make + // sure they no longer are keeping the screen frozen. if (r.visible) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Making invisible: " + r); + if (DEBUG_VISBILITY) Slog.v(TAG, "Making invisible: " + r); r.visible = false; try { - mService.mWindowManager.setAppVisibility(r.appToken, false); - if ((r.state == ActivityState.STOPPING - || r.state == ActivityState.STOPPED) - && r.app != null && r.app.thread != null) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Scheduling invisibility: " + r); - r.app.thread.scheduleWindowVisibility(r.appToken, false); + mWindowManager.setAppVisibility(r.appToken, false); + switch (r.state) { + case STOPPING: + case STOPPED: + if (r.app != null && r.app.thread != null) { + if (DEBUG_VISBILITY) Slog.v( + TAG, "Scheduling invisibility: " + r); + r.app.thread.scheduleWindowVisibility(r.appToken, false); + } + break; + + case INITIALIZING: + case RESUMED: + case PAUSING: + case PAUSED: + // This case created for transitioning activities from + // translucent to opaque {@link Activity#convertToOpaque}. + if (!mStackSupervisor.mStoppingActivities.contains(r)) { + mStackSupervisor.mStoppingActivities.add(r); + } + mStackSupervisor.scheduleIdleLocked(); + break; + + default: + break; } } catch (Exception e) { // Just skip on any failure; we'll make it @@ -1395,30 +1164,50 @@ final class ActivityStack { + r.intent.getComponent(), e); } } else { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Already invisible: " + r); + if (DEBUG_VISBILITY) Slog.v(TAG, "Already invisible: " + r); } - } else if (r.fullscreen) { - if (DEBUG_VISBILITY) Slog.v( - TAG, "Now behindFullscreen: " + r); - behindFullscreen = true; } } - i--; } + return showHomeBehindStack; + } + + void convertToTranslucent(ActivityRecord r) { + mTranslucentActivityWaiting = r; + mUndrawnActivitiesBelowTopTranslucent.clear(); + mHandler.sendEmptyMessageDelayed(TRANSLUCENT_TIMEOUT_MSG, TRANSLUCENT_CONVERSION_TIMEOUT); } /** - * Version of ensureActivitiesVisible that can easily be called anywhere. + * Called as activities below the top translucent activity are redrawn. When the last one is + * redrawn notify the top activity by calling + * {@link Activity#onTranslucentConversionComplete}. + * + * @param r The most recent background activity to be drawn. Or, if r is null then a timeout + * occurred and the activity will be notified immediately. */ - final void ensureActivitiesVisibleLocked(ActivityRecord starting, - int configChanges) { - ActivityRecord r = topRunningActivityLocked(null); - if (r != null) { - ensureActivitiesVisibleLocked(r, starting, null, configChanges); + void notifyActivityDrawnLocked(ActivityRecord r) { + if ((r == null) + || (mUndrawnActivitiesBelowTopTranslucent.remove(r) && + mUndrawnActivitiesBelowTopTranslucent.isEmpty())) { + // The last undrawn activity below the top has just been drawn. If there is an + // opaque activity at the top, notify it that it can become translucent safely now. + final ActivityRecord waitingActivity = mTranslucentActivityWaiting; + mTranslucentActivityWaiting = null; + mUndrawnActivitiesBelowTopTranslucent.clear(); + mHandler.removeMessages(TRANSLUCENT_TIMEOUT_MSG); + + if (waitingActivity != null && waitingActivity.app != null && + waitingActivity.app.thread != null) { + try { + waitingActivity.app.thread.scheduleTranslucentConversionComplete( + waitingActivity.appToken, r != null); + } catch (RemoteException e) { + } + } } } - + /** * Ensure that the top activity in the stack is resumed. * @@ -1433,47 +1222,79 @@ final class ActivityStack { } final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) { + if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen(""); + // Find the first activity that is not finishing. ActivityRecord next = topRunningActivityLocked(null); // Remember how we'll process this pause/resume situation, and ensure // that the state is reset however we wind up proceeding. - final boolean userLeaving = mUserLeaving; - mUserLeaving = false; + final boolean userLeaving = mStackSupervisor.mUserLeaving; + mStackSupervisor.mUserLeaving = false; if (next == null) { // There are no more activities! Let's just start up the // Launcher... - if (mMainStack) { - ActivityOptions.abort(options); - return mService.startHomeActivityLocked(mCurrentUser); - } + ActivityOptions.abort(options); + if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: No more activities go home"); + if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); + return mStackSupervisor.resumeHomeActivity(prev); } next.delayedResume = false; - + // If the top activity is the resumed one, nothing to do. - if (mResumedActivity == next && next.state == ActivityState.RESUMED) { + if (mResumedActivity == next && next.state == ActivityState.RESUMED && + mStackSupervisor.allResumedActivitiesComplete()) { // Make sure we have executed any pending transitions, since there // should be nothing left to do at this point. - mService.mWindowManager.executeAppTransition(); + mWindowManager.executeAppTransition(); mNoAnimActivities.clear(); ActivityOptions.abort(options); + if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Top activity resumed " + next); + if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return false; } + final TaskRecord nextTask = next.task; + final TaskRecord prevTask = prev != null ? prev.task : null; + if (prevTask != null && prevTask.mOnTopOfHome && prev.finishing && prev.frontOfTask) { + if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); + if (prevTask == nextTask) { + ArrayList<ActivityRecord> activities = prevTask.mActivities; + final int numActivities = activities.size(); + for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + // r is usually the same as next, but what if two activities were launched + // before prev finished? + if (!r.finishing) { + r.frontOfTask = true; + break; + } + } + } else if (prevTask != topTask()) { + // This task is going away but it was supposed to return to the home task. + // Now the task above it has to return to the home task instead. + final int taskNdx = mTaskHistory.indexOf(prevTask) + 1; + mTaskHistory.get(taskNdx).mOnTopOfHome = true; + } else { + if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Launching home next"); + return mStackSupervisor.resumeHomeActivity(prev); + } + } + // If we are sleeping, and there is no resumed activity, and the top // activity is paused, well that is the state we want. - if ((mService.mSleeping || mService.mShuttingDown) + if (mService.isSleepingOrShuttingDown() && mLastPausedActivity == next - && (next.state == ActivityState.PAUSED - || next.state == ActivityState.STOPPED - || next.state == ActivityState.STOPPING)) { + && mStackSupervisor.allPausedActivitiesComplete()) { // Make sure we have executed any pending transitions, since there // should be nothing left to do at this point. - mService.mWindowManager.executeAppTransition(); + mWindowManager.executeAppTransition(); mNoAnimActivities.clear(); ActivityOptions.abort(options); + if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Going to sleep and all paused"); + if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return false; } @@ -1483,15 +1304,16 @@ final class ActivityStack { if (mService.mStartedUsers.get(next.userId) == null) { Slog.w(TAG, "Skipping resume of top activity " + next + ": user " + next.userId + " is stopped"); + if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return false; } // The activity may be waiting for stop, but that is no longer // appropriate for it. - mStoppingActivities.remove(next); - mGoingToSleepActivities.remove(next); + mStackSupervisor.mStoppingActivities.remove(next); + mStackSupervisor.mGoingToSleepActivities.remove(next); next.sleeping = false; - mWaitingVisibleActivities.remove(next); + mStackSupervisor.mWaitingVisibleActivities.remove(next); next.updateOptionsLocked(options); @@ -1499,9 +1321,10 @@ final class ActivityStack { // If we are currently pausing an activity, then don't do anything // until that is done. - if (mPausingActivity != null) { - if (DEBUG_SWITCH || DEBUG_PAUSE) Slog.v(TAG, - "Skip resume: pausing=" + mPausingActivity); + if (!mStackSupervisor.allPausedActivitiesComplete()) { + if (DEBUG_SWITCH || DEBUG_PAUSE || DEBUG_STATES) Slog.v(TAG, + "resumeTopActivityLocked: Skip resume: some activity pausing."); + if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return false; } @@ -1533,11 +1356,18 @@ final class ActivityStack { mLastStartedActivity = next; } } - + // We need to start pausing the current activity so the top one // can be resumed... + boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving); if (mResumedActivity != null) { - if (DEBUG_SWITCH) Slog.v(TAG, "Skip resume: need to start pausing"); + pausing = true; + startPausingLocked(userLeaving, false); + if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Pausing " + mResumedActivity); + } + if (pausing) { + if (DEBUG_SWITCH || DEBUG_STATES) Slog.v(TAG, + "resumeTopActivityLocked: Skip resume: need to start pausing"); // At this point we want to put the upcoming activity's process // at the top of the LRU list, since we know we will be needing it // very soon and it would be a waste to let it get killed if it @@ -1545,31 +1375,28 @@ final class ActivityStack { if (next.app != null && next.app.thread != null) { // No reason to do full oom adj update here; we'll let that // happen whenever it needs to later. - mService.updateLruProcessLocked(next.app, false); + mService.updateLruProcessLocked(next.app, false, true); } - startPausingLocked(userLeaving, false); + if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return true; } // If the most recent activity was noHistory but was only stopped rather // than stopped+finished because the device went to sleep, we need to make // sure to finish it as we're making a new activity topmost. - final ActivityRecord last = mLastPausedActivity; - if (mService.mSleeping && last != null && !last.finishing) { - if ((last.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 - || (last.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) { - if (DEBUG_STATES) { - Slog.d(TAG, "no-history finish of " + last + " on new resume"); - } - requestFinishActivityLocked(last.appToken, Activity.RESULT_CANCELED, null, - "no-history", false); - } + if (mService.mSleeping && mLastNoHistoryActivity != null && + !mLastNoHistoryActivity.finishing) { + if (DEBUG_STATES) Slog.d(TAG, "no-history finish of " + mLastNoHistoryActivity + + " on new resume"); + requestFinishActivityLocked(mLastNoHistoryActivity.appToken, Activity.RESULT_CANCELED, + null, "no-history", false); + mLastNoHistoryActivity = null; } if (prev != null && prev != next) { if (!prev.waitingVisible && next != null && !next.nowVisible) { prev.waitingVisible = true; - mWaitingVisibleActivities.add(prev); + mStackSupervisor.mWaitingVisibleActivities.add(prev); if (DEBUG_SWITCH) Slog.v( TAG, "Resuming top, waiting visible to hide: " + prev); } else { @@ -1582,7 +1409,7 @@ final class ActivityStack { // previous should actually be hidden depending on whether the // new one is found to be full-screen or not. if (prev.finishing) { - mService.mWindowManager.setAppVisibility(prev.appToken, false); + mWindowManager.setAppVisibility(prev.appToken, false); if (DEBUG_SWITCH) Slog.v(TAG, "Not waiting for visible to hide: " + prev + ", waitingVisible=" + (prev != null ? prev.waitingVisible : null) @@ -1610,120 +1437,114 @@ final class ActivityStack { // We are starting up the next activity, so tell the window manager // that the previous one will be hidden soon. This way it can know // to ignore it when computing the desired screen orientation. - boolean noAnim = false; + boolean anim = true; if (prev != null) { if (prev.finishing) { if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare close transition: prev=" + prev); if (mNoAnimActivities.contains(prev)) { - mService.mWindowManager.prepareAppTransition( - AppTransition.TRANSIT_NONE, false); + anim = false; + mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false); } else { - mService.mWindowManager.prepareAppTransition(prev.task == next.task + mWindowManager.prepareAppTransition(prev.task == next.task ? AppTransition.TRANSIT_ACTIVITY_CLOSE : AppTransition.TRANSIT_TASK_CLOSE, false); } - mService.mWindowManager.setAppWillBeHidden(prev.appToken); - mService.mWindowManager.setAppVisibility(prev.appToken, false); + mWindowManager.setAppWillBeHidden(prev.appToken); + mWindowManager.setAppVisibility(prev.appToken, false); } else { - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare open transition: prev=" + prev); + if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: prev=" + prev); if (mNoAnimActivities.contains(next)) { - noAnim = true; - mService.mWindowManager.prepareAppTransition( - AppTransition.TRANSIT_NONE, false); + anim = false; + mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false); } else { - mService.mWindowManager.prepareAppTransition(prev.task == next.task + mWindowManager.prepareAppTransition(prev.task == next.task ? AppTransition.TRANSIT_ACTIVITY_OPEN : AppTransition.TRANSIT_TASK_OPEN, false); } } if (false) { - mService.mWindowManager.setAppWillBeHidden(prev.appToken); - mService.mWindowManager.setAppVisibility(prev.appToken, false); + mWindowManager.setAppWillBeHidden(prev.appToken); + mWindowManager.setAppVisibility(prev.appToken, false); } - } else if (mHistory.size() > 1) { - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare open transition: no previous"); + } else { + if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: no previous"); if (mNoAnimActivities.contains(next)) { - noAnim = true; - mService.mWindowManager.prepareAppTransition( - AppTransition.TRANSIT_NONE, false); + anim = false; + mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false); } else { - mService.mWindowManager.prepareAppTransition( - AppTransition.TRANSIT_ACTIVITY_OPEN, false); + mWindowManager.prepareAppTransition(AppTransition.TRANSIT_ACTIVITY_OPEN, false); } } - if (!noAnim) { + if (anim) { next.applyOptionsLocked(); } else { next.clearOptionsLocked(); } + ActivityStack lastStack = mStackSupervisor.getLastStack(); if (next.app != null && next.app.thread != null) { if (DEBUG_SWITCH) Slog.v(TAG, "Resume running: " + next); // This activity is now becoming visible. - mService.mWindowManager.setAppVisibility(next.appToken, true); + mWindowManager.setAppVisibility(next.appToken, true); // schedule launch ticks to collect information about slow apps. next.startLaunchTickingLocked(); - ActivityRecord lastResumedActivity = mResumedActivity; + ActivityRecord lastResumedActivity = + lastStack == null ? null :lastStack.mResumedActivity; ActivityState lastState = next.state; mService.updateCpuStats(); - + if (DEBUG_STATES) Slog.v(TAG, "Moving to RESUMED: " + next + " (in existing)"); next.state = ActivityState.RESUMED; mResumedActivity = next; next.task.touchActiveTime(); - if (mMainStack) { - mService.addRecentTaskLocked(next.task); - } - mService.updateLruProcessLocked(next.app, true); + mService.addRecentTaskLocked(next.task); + mService.updateLruProcessLocked(next.app, true, true); updateLRUListLocked(next); // Have the window manager re-evaluate the orientation of // the screen based on the new activity order. - boolean updated = false; - if (mMainStack) { - synchronized (mService) { - Configuration config = mService.mWindowManager.updateOrientationFromAppTokens( - mService.mConfiguration, - next.mayFreezeScreenLocked(next.app) ? next.appToken : null); - if (config != null) { - next.frozenBeforeDestroy = true; - } - updated = mService.updateConfigurationLocked(config, next, false, false); + boolean notUpdated = true; + if (mStackSupervisor.isFrontStack(this)) { + Configuration config = mWindowManager.updateOrientationFromAppTokens( + mService.mConfiguration, + next.mayFreezeScreenLocked(next.app) ? next.appToken : null); + if (config != null) { + next.frozenBeforeDestroy = true; } + notUpdated = !mService.updateConfigurationLocked(config, next, false, false); } - if (!updated) { + + if (notUpdated) { // 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 // is still at the top and schedule another run if something // weird happened. ActivityRecord nextNext = topRunningActivityLocked(null); - if (DEBUG_SWITCH) Slog.i(TAG, + if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG, "Activity config changed during resume: " + next + ", new next: " + nextNext); if (nextNext != next) { // Do over! - mHandler.sendEmptyMessage(RESUME_TOP_ACTIVITY_MSG); + mStackSupervisor.scheduleResumeTopActivities(); } - if (mMainStack) { - mService.setFocusedActivityLocked(next); + if (mStackSupervisor.reportResumedActivityLocked(next)) { + mNoAnimActivities.clear(); + if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); + return true; } - ensureActivitiesVisibleLocked(null, 0); - mService.mWindowManager.executeAppTransition(); - mNoAnimActivities.clear(); - return true; + if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); + return false; } - + try { // Deliver all pending results. - ArrayList a = next.results; + ArrayList<ResultInfo> a = next.results; if (a != null) { final int N = a.size(); if (!next.finishing && N > 0) { @@ -1741,36 +1562,38 @@ final class ActivityStack { EventLog.writeEvent(EventLogTags.AM_RESUME_ACTIVITY, next.userId, System.identityHashCode(next), next.task.taskId, next.shortComponentName); - + next.sleeping = false; - showAskCompatModeDialogLocked(next); + mService.showAskCompatModeDialogLocked(next); next.app.pendingUiClean = true; - next.app.thread.scheduleResumeActivity(next.appToken, + next.app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP); + next.app.thread.scheduleResumeActivity(next.appToken, next.app.repProcState, mService.isNextTransitionForward()); - - checkReadyForSleepLocked(); + mStackSupervisor.checkReadyForSleepLocked(); + + if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Resumed " + next); } catch (Exception e) { // Whoops, need to restart this activity! if (DEBUG_STATES) Slog.v(TAG, "Resume failed; resetting state to " + lastState + ": " + next); next.state = lastState; - mResumedActivity = lastResumedActivity; + if (lastStack != null) { + lastStack.mResumedActivity = lastResumedActivity; + } Slog.i(TAG, "Restarting because process died: " + next); if (!next.hasBeenLaunched) { next.hasBeenLaunched = true; - } else { - if (SHOW_APP_STARTING_PREVIEW && mMainStack) { - mService.mWindowManager.setAppStartingWindow( - next.appToken, next.packageName, next.theme, - mService.compatibilityInfoForPackageLocked( - next.info.applicationInfo), - next.nonLocalizedLabel, - next.labelRes, next.icon, next.windowFlags, - null, true); - } + } else if (SHOW_APP_STARTING_PREVIEW && lastStack != null && + mStackSupervisor.isFrontStack(lastStack)) { + mWindowManager.setAppStartingWindow( + next.appToken, next.packageName, next.theme, + mService.compatibilityInfoForPackageLocked(next.info.applicationInfo), + next.nonLocalizedLabel, next.labelRes, next.icon, next.logo, + next.windowFlags, null, true); } - startSpecificActivityLocked(next, true, false); + mStackSupervisor.startSpecificActivityLocked(next, true, false); + if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return true; } @@ -1785,6 +1608,7 @@ final class ActivityStack { Slog.w(TAG, "Exception thrown during resume of " + next, e); requestFinishActivityLocked(next.appToken, Activity.RESULT_CANCELED, null, "resume-exception", true); + if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return true; } next.stopped = false; @@ -1795,53 +1619,78 @@ final class ActivityStack { next.hasBeenLaunched = true; } else { if (SHOW_APP_STARTING_PREVIEW) { - mService.mWindowManager.setAppStartingWindow( + mWindowManager.setAppStartingWindow( next.appToken, next.packageName, next.theme, mService.compatibilityInfoForPackageLocked( next.info.applicationInfo), next.nonLocalizedLabel, - next.labelRes, next.icon, next.windowFlags, + next.labelRes, next.icon, next.logo, next.windowFlags, null, true); } if (DEBUG_SWITCH) Slog.v(TAG, "Restarting: " + next); } - startSpecificActivityLocked(next, true, true); + if (DEBUG_STATES) Slog.d(TAG, "resumeTopActivityLocked: Restarting " + next); + mStackSupervisor.startSpecificActivityLocked(next, true, true); } + if (DEBUG_STACK) mStackSupervisor.validateTopActivitiesLocked(); return true; } - private final void startActivityLocked(ActivityRecord r, boolean newTask, - boolean doResume, boolean keepCurTransition, Bundle options) { - final int NH = mHistory.size(); + private void insertTaskAtTop(TaskRecord task) { + // If this is being moved to the top by another activity or being launched from the home + // activity, set mOnTopOfHome accordingly. + ActivityStack lastStack = mStackSupervisor.getLastStack(); + final boolean fromHome = lastStack == null ? true : lastStack.isHomeStack(); + if (!isHomeStack() && (fromHome || topTask() != task)) { + task.mOnTopOfHome = fromHome; + } + + mTaskHistory.remove(task); + // Now put task at top. + int stackNdx = mTaskHistory.size(); + if (task.userId != mCurrentUser) { + // Put non-current user tasks below current user tasks. + while (--stackNdx >= 0) { + if (mTaskHistory.get(stackNdx).userId != mCurrentUser) { + break; + } + } + ++stackNdx; + } + mTaskHistory.add(stackNdx, task); + } - int addPos = -1; - + final void startActivityLocked(ActivityRecord r, boolean newTask, + boolean doResume, boolean keepCurTransition, Bundle options) { + TaskRecord rTask = r.task; + final int taskId = rTask.taskId; + if (taskForIdLocked(taskId) == null || newTask) { + // Last activity in task had been removed or ActivityManagerService is reusing task. + // Insert or replace. + // Might not even be in. + insertTaskAtTop(rTask); + mWindowManager.moveTaskToTop(taskId); + } + TaskRecord task = null; if (!newTask) { // If starting in an existing task, find where that is... boolean startIt = true; - for (int i = NH-1; i >= 0; i--) { - ActivityRecord p = mHistory.get(i); - if (p.finishing) { - continue; - } - if (p.task == r.task) { + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + task = mTaskHistory.get(taskNdx); + if (task == r.task) { // Here it is! Now, if this is not yet visible to the // user, then just add it without starting; it will // get started when the user navigates back to it. - addPos = i+1; if (!startIt) { - if (DEBUG_ADD_REMOVE) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.i(TAG, "Adding activity " + r + " to stack at " + addPos, - here); - } - mHistory.add(addPos, r); + if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to task " + + task, new RuntimeException("here").fillInStackTrace()); + task.addActivityToTop(r); r.putInHistory(); - mService.mWindowManager.addAppToken(addPos, r.appToken, r.task.taskId, - r.info.screenOrientation, r.fullscreen, - (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0); + mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, + r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, + (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, + r.userId); if (VALIDATE_TOKENS) { validateAppTokensLocked(); } @@ -1849,8 +1698,7 @@ final class ActivityStack { return; } break; - } - if (p.fullscreen) { + } else if (task.numFullscreen > 0) { startIt = false; } } @@ -1858,28 +1706,26 @@ final class ActivityStack { // Place a new activity at top of stack, so it is next to interact // with the user. - if (addPos < 0) { - addPos = NH; - } - + // If we are not placing the new activity frontmost, we do not want // to deliver the onUserLeaving callback to the actual frontmost // activity - if (addPos < NH) { - mUserLeaving = false; - if (DEBUG_USER_LEAVING) Slog.v(TAG, "startActivity() behind front, mUserLeaving=false"); + if (task == r.task && mTaskHistory.indexOf(task) != (mTaskHistory.size() - 1)) { + mStackSupervisor.mUserLeaving = false; + if (DEBUG_USER_LEAVING) Slog.v(TAG, + "startActivity() behind front, mUserLeaving=false"); } - + + task = r.task; + // Slot the activity into the history stack and proceed - if (DEBUG_ADD_REMOVE) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.i(TAG, "Adding activity " + r + " to stack at " + addPos, here); - } - mHistory.add(addPos, r); + if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Adding activity " + r + " to stack to task " + task, + new RuntimeException("here").fillInStackTrace()); + task.addActivityToTop(r); + r.putInHistory(); r.frontOfTask = newTask; - if (NH > 0) { + if (!isHomeStack() || numActivities() > 0) { // We want to show the starting preview window if we are // switching to a new task, or the next activity's process is // not currently running. @@ -1894,19 +1740,18 @@ final class ActivityStack { if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare open transition: starting " + r); if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mService.mWindowManager.prepareAppTransition( - AppTransition.TRANSIT_NONE, keepCurTransition); + mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, keepCurTransition); mNoAnimActivities.add(r); } else { - mService.mWindowManager.prepareAppTransition(newTask + mWindowManager.prepareAppTransition(newTask ? AppTransition.TRANSIT_TASK_OPEN : AppTransition.TRANSIT_ACTIVITY_OPEN, keepCurTransition); mNoAnimActivities.remove(r); } r.updateOptionsLocked(options); - mService.mWindowManager.addAppToken( - addPos, r.appToken, r.task.taskId, r.info.screenOrientation, r.fullscreen, - (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0); + mWindowManager.addAppToken(task.mActivities.indexOf(r), + r.appToken, r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, + (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId); boolean doShow = true; if (newTask) { // Even though this activity is starting fresh, we still need @@ -1929,23 +1774,27 @@ final class ActivityStack { if (prev != null) { // We don't want to reuse the previous starting preview if: // (1) The current activity is in a different task. - if (prev.task != r.task) prev = null; + if (prev.task != r.task) { + prev = null; + } // (2) The current activity is already displayed. - else if (prev.nowVisible) prev = null; + else if (prev.nowVisible) { + prev = null; + } } - mService.mWindowManager.setAppStartingWindow( + mWindowManager.setAppStartingWindow( r.appToken, r.packageName, r.theme, mService.compatibilityInfoForPackageLocked( r.info.applicationInfo), r.nonLocalizedLabel, - r.labelRes, r.icon, r.windowFlags, + r.labelRes, r.icon, r.logo, r.windowFlags, prev != null ? prev.appToken : null, showStartingIcon); } } else { // If this is the first activity, don't do any fancy animations, // because there is nothing for it to animate on top of. - mService.mWindowManager.addAppToken(addPos, r.appToken, r.task.taskId, - r.info.screenOrientation, r.fullscreen, - (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0); + mWindowManager.addAppToken(task.mActivities.indexOf(r), r.appToken, + r.task.taskId, mStackId, r.info.screenOrientation, r.fullscreen, + (r.info.flags & ActivityInfo.FLAG_SHOW_ON_LOCK_SCREEN) != 0, r.userId); ActivityOptions.abort(options); } if (VALIDATE_TOKENS) { @@ -1953,233 +1802,217 @@ final class ActivityStack { } if (doResume) { - resumeTopActivityLocked(null); + mStackSupervisor.resumeTopActivitiesLocked(); } } final void validateAppTokensLocked() { mValidateAppTokens.clear(); - mValidateAppTokens.ensureCapacity(mHistory.size()); - for (int i=0; i<mHistory.size(); i++) { - mValidateAppTokens.add(mHistory.get(i).appToken); + mValidateAppTokens.ensureCapacity(numActivities()); + final int numTasks = mTaskHistory.size(); + for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) { + TaskRecord task = mTaskHistory.get(taskNdx); + final ArrayList<ActivityRecord> activities = task.mActivities; + if (activities.isEmpty()) { + continue; + } + TaskGroup group = new TaskGroup(); + group.taskId = task.taskId; + mValidateAppTokens.add(group); + final int numActivities = activities.size(); + for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + group.tokens.add(r.appToken); + } } - mService.mWindowManager.validateAppTokens(mValidateAppTokens); + mWindowManager.validateAppTokens(mStackId, mValidateAppTokens); } /** * Perform a reset of the given task, if needed as part of launching it. * Returns the new HistoryRecord at the top of the task. */ - private final ActivityRecord resetTaskIfNeededLocked(ActivityRecord taskTop, - ActivityRecord newActivity) { - boolean forceReset = (newActivity.info.flags - &ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0; - if (ACTIVITY_INACTIVE_RESET_TIME > 0 - && taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) { - if ((newActivity.info.flags - &ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) { - forceReset = true; - } - } - - final TaskRecord task = taskTop.task; - - // We are going to move through the history list so that we can look - // at each activity 'target' with 'below' either the interesting - // activity immediately below it in the stack or null. - ActivityRecord target = null; - int targetI = 0; - int taskTopI = -1; - int replyChainEnd = -1; - int lastReparentPos = -1; + /** + * Helper method for #resetTaskIfNeededLocked. + * We are inside of the task being reset... we'll either finish this activity, push it out + * for another task, or leave it as-is. + * @param task The task containing the Activity (taskTop) that might be reset. + * @param forceReset + * @return An ActivityOptions that needs to be processed. + */ + final ActivityOptions resetTargetTaskIfNeededLocked(TaskRecord task, boolean forceReset) { ActivityOptions topOptions = null; + + int replyChainEnd = -1; boolean canMoveOptions = true; - for (int i=mHistory.size()-1; i>=-1; i--) { - ActivityRecord below = i >= 0 ? mHistory.get(i) : null; - - if (below != null && below.finishing) { - continue; - } - // Don't check any lower in the stack if we're crossing a user boundary. - if (below != null && below.userId != taskTop.userId) { - break; - } - if (target == null) { - target = below; - targetI = i; - // If we were in the middle of a reply chain before this - // task, it doesn't appear like the root of the chain wants - // anything interesting, so drop it. - replyChainEnd = -1; - continue; - } - + + // We only do this for activities that are not the root of the task (since if we finish + // the root, we may no longer have the task!). + final ArrayList<ActivityRecord> activities = task.mActivities; + final int numActivities = activities.size(); + for (int i = numActivities - 1; i > 0; --i ) { + ActivityRecord target = activities.get(i); + final int flags = target.info.flags; - final boolean finishOnTaskLaunch = - (flags&ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0; + (flags & ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0; final boolean allowTaskReparenting = - (flags&ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) != 0; - - if (target.task == task) { - // We are inside of the task being reset... we'll either - // finish this activity, push it out for another task, - // or leave it as-is. We only do this - // for activities that are not the root of the task (since - // if we finish the root, we may no longer have the task!). - if (taskTopI < 0) { - taskTopI = targetI; - } - if (below != null && below.task == task) { - final boolean clearWhenTaskReset = - (target.intent.getFlags() - &Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0; - if (!finishOnTaskLaunch && !clearWhenTaskReset && target.resultTo != null) { - // If this activity is sending a reply to a previous - // activity, we can't do anything with it now until - // we reach the start of the reply chain. - // XXX note that we are assuming the result is always - // to the previous activity, which is almost always - // the case but we really shouldn't count on. - if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - } else if (!finishOnTaskLaunch && !clearWhenTaskReset && allowTaskReparenting - && target.taskAffinity != null - && !target.taskAffinity.equals(task.affinity)) { - // If this activity has an affinity for another - // task, then we need to move it out of here. We will - // move it as far out of the way as possible, to the - // bottom of the activity stack. This also keeps it - // correctly ordered with any activities we previously - // moved. - ActivityRecord p = mHistory.get(0); - if (target.taskAffinity != null - && target.taskAffinity.equals(p.task.affinity)) { - // If the activity currently at the bottom has the - // same task affinity as the one we are moving, - // then merge it into the same task. - target.setTask(p.task, p.thumbHolder, false); - if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target - + " out to bottom task " + p.task); - } else { - mService.mCurTask++; - if (mService.mCurTask <= 0) { - mService.mCurTask = 1; - } - target.setTask(new TaskRecord(mService.mCurTask, target.info, null), - null, false); - target.task.affinityIntent = target.intent; - if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target - + " out to new task " + target.task); - } - mService.mWindowManager.setAppGroupId(target.appToken, task.taskId); - if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - int dstPos = 0; - ThumbnailHolder curThumbHolder = target.thumbHolder; - boolean gotOptions = !canMoveOptions; - for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { - p = mHistory.get(srcPos); - if (p.finishing) { - continue; - } - if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p - + " out to target's task " + target.task); - p.setTask(target.task, curThumbHolder, false); - curThumbHolder = p.thumbHolder; - canMoveOptions = false; - if (!gotOptions && topOptions == null) { - topOptions = p.takeOptionsLocked(); - if (topOptions != null) { - gotOptions = true; - } - } - if (DEBUG_ADD_REMOVE) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.i(TAG, "Removing and adding activity " + p + " to stack at " - + dstPos, here); - } - mHistory.remove(srcPos); - mHistory.add(dstPos, p); - mService.mWindowManager.moveAppToken(dstPos, p.appToken); - mService.mWindowManager.setAppGroupId(p.appToken, p.task.taskId); - dstPos++; - if (VALIDATE_TOKENS) { - validateAppTokensLocked(); - } - i++; - } - if (taskTop == p) { - taskTop = below; - } - if (taskTopI == replyChainEnd) { - taskTopI = -1; - } - replyChainEnd = -1; - } else if (forceReset || finishOnTaskLaunch - || clearWhenTaskReset) { - // If the activity should just be removed -- either - // because it asks for it, or the task should be - // cleared -- then finish it and anything that is - // part of its reply chain. - if (clearWhenTaskReset) { - // In this case, we want to finish this activity - // and everything above it, so be sneaky and pretend - // like these are all in the reply chain. - replyChainEnd = targetI+1; - while (replyChainEnd < mHistory.size() && - (mHistory.get( - replyChainEnd)).task == task) { - replyChainEnd++; - } - replyChainEnd--; - } else if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - ActivityRecord p = null; - boolean gotOptions = !canMoveOptions; - for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { - p = mHistory.get(srcPos); - if (p.finishing) { - continue; - } - canMoveOptions = false; - if (!gotOptions && topOptions == null) { - topOptions = p.takeOptionsLocked(); - if (topOptions != null) { - gotOptions = true; - } - } - if (finishActivityLocked(p, srcPos, - Activity.RESULT_CANCELED, null, "reset", false)) { - replyChainEnd--; - srcPos--; - } - } - if (taskTop == p) { - taskTop = below; - } - if (taskTopI == replyChainEnd) { - taskTopI = -1; + (flags & ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) != 0; + final boolean clearWhenTaskReset = + (target.intent.getFlags() & Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0; + + if (!finishOnTaskLaunch + && !clearWhenTaskReset + && target.resultTo != null) { + // If this activity is sending a reply to a previous + // activity, we can't do anything with it now until + // we reach the start of the reply chain. + // XXX note that we are assuming the result is always + // to the previous activity, which is almost always + // the case but we really shouldn't count on. + if (replyChainEnd < 0) { + replyChainEnd = i; + } + } else if (!finishOnTaskLaunch + && !clearWhenTaskReset + && allowTaskReparenting + && target.taskAffinity != null + && !target.taskAffinity.equals(task.affinity)) { + // If this activity has an affinity for another + // task, then we need to move it out of here. We will + // move it as far out of the way as possible, to the + // bottom of the activity stack. This also keeps it + // correctly ordered with any activities we previously + // moved. + final ActivityRecord bottom = + !mTaskHistory.isEmpty() && !mTaskHistory.get(0).mActivities.isEmpty() ? + mTaskHistory.get(0).mActivities.get(0) : null; + if (bottom != null && target.taskAffinity != null + && target.taskAffinity.equals(bottom.task.affinity)) { + // If the activity currently at the bottom has the + // same task affinity as the one we are moving, + // then merge it into the same task. + target.setTask(bottom.task, bottom.thumbHolder, false); + if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + + " out to bottom task " + bottom.task); + } else { + target.setTask(createTaskRecord(mStackSupervisor.getNextTaskId(), target.info, + null, false), null, false); + target.task.affinityIntent = target.intent; + if (DEBUG_TASKS) Slog.v(TAG, "Start pushing activity " + target + + " out to new task " + target.task); + } + + final TaskRecord targetTask = target.task; + final int targetTaskId = targetTask.taskId; + mWindowManager.setAppGroupId(target.appToken, targetTaskId); + + boolean noOptions = canMoveOptions; + final int start = replyChainEnd < 0 ? i : replyChainEnd; + for (int srcPos = start; srcPos >= i; --srcPos) { + final ActivityRecord p = activities.get(srcPos); + if (p.finishing) { + continue; + } + + ThumbnailHolder curThumbHolder = p.thumbHolder; + canMoveOptions = false; + if (noOptions && topOptions == null) { + topOptions = p.takeOptionsLocked(); + if (topOptions != null) { + noOptions = false; } - replyChainEnd = -1; - } else { - // If we were in the middle of a chain, well the - // activity that started it all doesn't want anything - // special, so leave it all as-is. - replyChainEnd = -1; } + if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing activity " + p + " from task=" + + task + " adding to task=" + targetTask, + new RuntimeException("here").fillInStackTrace()); + if (DEBUG_TASKS) Slog.v(TAG, "Pushing next activity " + p + + " out to target's task " + target.task); + p.setTask(targetTask, curThumbHolder, false); + targetTask.addActivityAtBottom(p); + + mWindowManager.setAppGroupId(p.appToken, targetTaskId); + } + + mWindowManager.moveTaskToBottom(targetTaskId); + if (VALIDATE_TOKENS) { + validateAppTokensLocked(); + } + + replyChainEnd = -1; + } else if (forceReset || finishOnTaskLaunch || clearWhenTaskReset) { + // If the activity should just be removed -- either + // because it asks for it, or the task should be + // cleared -- then finish it and anything that is + // part of its reply chain. + int end; + if (clearWhenTaskReset) { + // In this case, we want to finish this activity + // and everything above it, so be sneaky and pretend + // like these are all in the reply chain. + end = numActivities - 1; + } else if (replyChainEnd < 0) { + end = i; } else { - // Reached the bottom of the task -- any reply chain - // should be left as-is. - replyChainEnd = -1; + end = replyChainEnd; + } + boolean noOptions = canMoveOptions; + for (int srcPos = i; srcPos <= end; srcPos++) { + ActivityRecord p = activities.get(srcPos); + if (p.finishing) { + continue; + } + canMoveOptions = false; + if (noOptions && topOptions == null) { + topOptions = p.takeOptionsLocked(); + if (topOptions != null) { + noOptions = false; + } + } + if (DEBUG_TASKS) Slog.w(TAG, + "resetTaskIntendedTask: calling finishActivity on " + p); + if (finishActivityLocked(p, Activity.RESULT_CANCELED, null, "reset", false)) { + end--; + srcPos--; + } } + replyChainEnd = -1; + } else { + // If we were in the middle of a chain, well the + // activity that started it all doesn't want anything + // special, so leave it all as-is. + replyChainEnd = -1; + } + } + + return topOptions; + } - } else if (target.resultTo != null && (below == null - || below.task == target.task)) { + /** + * Helper method for #resetTaskIfNeededLocked. Processes all of the activities in a given + * TaskRecord looking for an affinity with the task of resetTaskIfNeededLocked.taskTop. + * @param affinityTask The task we are looking for an affinity to. + * @param task Task that resetTaskIfNeededLocked.taskTop belongs to. + * @param topTaskIsHigher True if #task has already been processed by resetTaskIfNeededLocked. + * @param forceReset Flag passed in to resetTaskIfNeededLocked. + */ + private int resetAffinityTaskIfNeededLocked(TaskRecord affinityTask, TaskRecord task, + boolean topTaskIsHigher, boolean forceReset, int taskInsertionPoint) { + int replyChainEnd = -1; + final int taskId = task.taskId; + final String taskAffinity = task.affinity; + + final ArrayList<ActivityRecord> activities = affinityTask.mActivities; + final int numActivities = activities.size(); + // Do not operate on the root Activity. + for (int i = numActivities - 1; i > 0; --i) { + ActivityRecord target = activities.get(i); + + final int flags = target.info.flags; + boolean finishOnTaskLaunch = (flags & ActivityInfo.FLAG_FINISH_ON_TASK_LAUNCH) != 0; + boolean allowTaskReparenting = (flags & ActivityInfo.FLAG_ALLOW_TASK_REPARENTING) != 0; + + if (target.resultTo != null) { // If this activity is sending a reply to a previous // activity, we can't do anything with it now until // we reach the start of the reply chain. @@ -2187,14 +2020,13 @@ final class ActivityStack { // to the previous activity, which is almost always // the case but we really shouldn't count on. if (replyChainEnd < 0) { - replyChainEnd = targetI; + replyChainEnd = i; } - - } else if (taskTopI >= 0 && allowTaskReparenting - && task.affinity != null - && task.affinity.equals(target.taskAffinity)) { - // We are inside of another task... if this activity has - // an affinity for our task, then either remove it if we are + } else if (topTaskIsHigher + && allowTaskReparenting + && taskAffinity != null + && taskAffinity.equals(target.taskAffinity)) { + // This activity has an affinity for our task. Either remove it if we are // clearing or move it over to our task. Note that // we currently punt on the case where we are resetting a // task that is not at the top but who has activities above @@ -2205,1167 +2037,115 @@ final class ActivityStack { // someone starts an activity in a new task from an activity // in a task that is not currently on top.) if (forceReset || finishOnTaskLaunch) { - if (replyChainEnd < 0) { - replyChainEnd = targetI; - } - ActivityRecord p = null; - if (DEBUG_TASKS) Slog.v(TAG, "Finishing task at index " - + targetI + " to " + replyChainEnd); - for (int srcPos=targetI; srcPos<=replyChainEnd; srcPos++) { - p = mHistory.get(srcPos); + final int start = replyChainEnd >= 0 ? replyChainEnd : i; + if (DEBUG_TASKS) Slog.v(TAG, "Finishing task at index " + start + " to " + i); + for (int srcPos = start; srcPos >= i; --srcPos) { + final ActivityRecord p = activities.get(srcPos); if (p.finishing) { continue; } - if (finishActivityLocked(p, srcPos, - Activity.RESULT_CANCELED, null, "reset", false)) { - taskTopI--; - lastReparentPos--; - replyChainEnd--; - srcPos--; - } + finishActivityLocked(p, Activity.RESULT_CANCELED, null, "reset", false); } - replyChainEnd = -1; } else { - if (replyChainEnd < 0) { - replyChainEnd = targetI; + if (taskInsertionPoint < 0) { + taskInsertionPoint = task.mActivities.size(); + } - if (DEBUG_TASKS) Slog.v(TAG, "Reparenting task at index " - + targetI + " to " + replyChainEnd); - for (int srcPos=replyChainEnd; srcPos>=targetI; srcPos--) { - ActivityRecord p = mHistory.get(srcPos); - if (p.finishing) { - continue; - } - if (lastReparentPos < 0) { - lastReparentPos = taskTopI; - taskTop = p; - } else { - lastReparentPos--; - } - if (DEBUG_ADD_REMOVE) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.i(TAG, "Removing and adding activity " + p + " to stack at " - + lastReparentPos, here); - } - mHistory.remove(srcPos); + + final int start = replyChainEnd >= 0 ? replyChainEnd : i; + if (DEBUG_TASKS) Slog.v(TAG, "Reparenting from task=" + affinityTask + ":" + + start + "-" + i + " to task=" + task + ":" + taskInsertionPoint); + for (int srcPos = start; srcPos >= i; --srcPos) { + final ActivityRecord p = activities.get(srcPos); p.setTask(task, null, false); - mHistory.add(lastReparentPos, p); - if (DEBUG_TASKS) Slog.v(TAG, "Pulling activity " + p - + " from " + srcPos + " to " + lastReparentPos + task.addActivityAtIndex(taskInsertionPoint, p); + + if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing and adding activity " + p + + " to stack at " + task, + new RuntimeException("here").fillInStackTrace()); + if (DEBUG_TASKS) Slog.v(TAG, "Pulling activity " + p + " from " + srcPos + " in to resetting task " + task); - mService.mWindowManager.moveAppToken(lastReparentPos, p.appToken); - mService.mWindowManager.setAppGroupId(p.appToken, p.task.taskId); - if (VALIDATE_TOKENS) { - validateAppTokensLocked(); - } + mWindowManager.setAppGroupId(p.appToken, taskId); } - replyChainEnd = -1; - + mWindowManager.moveTaskToTop(taskId); + if (VALIDATE_TOKENS) { + validateAppTokensLocked(); + } + // Now we've moved it in to place... but what if this is // a singleTop activity and we have put it on top of another // instance of the same activity? Then we drop the instance // below so it remains singleTop. if (target.info.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) { - for (int j=lastReparentPos-1; j>=0; j--) { - ActivityRecord p = mHistory.get(j); - if (p.finishing) { - continue; - } + ArrayList<ActivityRecord> taskActivities = task.mActivities; + int targetNdx = taskActivities.indexOf(target); + if (targetNdx > 0) { + ActivityRecord p = taskActivities.get(targetNdx - 1); if (p.intent.getComponent().equals(target.intent.getComponent())) { - if (finishActivityLocked(p, j, - Activity.RESULT_CANCELED, null, "replace", false)) { - taskTopI--; - lastReparentPos--; - } + finishActivityLocked(p, Activity.RESULT_CANCELED, null, "replace", + false); } } } } - } else if (below != null && below.task != target.task) { - // We hit the botton of a task; the reply chain can't - // pass through it. replyChainEnd = -1; } - - target = below; - targetI = i; - } - - if (topOptions != null) { - // If we got some ActivityOptions from an activity on top that - // was removed from the task, propagate them to the new real top. - if (taskTop != null) { - taskTop.updateOptionsLocked(topOptions); - } else { - topOptions.abort(); - } - } - - return taskTop; - } - - /** - * Perform clear operation as requested by - * {@link Intent#FLAG_ACTIVITY_CLEAR_TOP}: search from the top of the - * stack to the given task, then look for - * an instance of that activity in the stack and, if found, finish all - * activities on top of it and return the instance. - * - * @param newR Description of the new activity being started. - * @return Returns the old activity that should be continued to be used, - * or null if none was found. - */ - private final ActivityRecord performClearTaskLocked(int taskId, - ActivityRecord newR, int launchFlags) { - int i = mHistory.size(); - - // First find the requested task. - while (i > 0) { - i--; - ActivityRecord r = mHistory.get(i); - if (r.task.taskId == taskId) { - i++; - break; - } - } - - // Now clear it. - while (i > 0) { - i--; - ActivityRecord r = mHistory.get(i); - if (r.finishing) { - continue; - } - if (r.task.taskId != taskId) { - return null; - } - if (r.realActivity.equals(newR.realActivity)) { - // Here it is! Now finish everything in front... - ActivityRecord ret = r; - while (i < (mHistory.size()-1)) { - i++; - r = mHistory.get(i); - if (r.task.taskId != taskId) { - break; - } - if (r.finishing) { - continue; - } - ActivityOptions opts = r.takeOptionsLocked(); - if (opts != null) { - ret.updateOptionsLocked(opts); - } - if (finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "clear", false)) { - i--; - } - } - - // 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 - && (launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0) { - if (!ret.finishing) { - int index = indexOfTokenLocked(ret.appToken); - if (index >= 0) { - finishActivityLocked(ret, index, Activity.RESULT_CANCELED, - null, "clear", false); - } - return null; - } - } - - return ret; - } - } - - return null; - } - - /** - * Completely remove all activities associated with an existing - * task starting at a specified index. - */ - private final void performClearTaskAtIndexLocked(int taskId, int i) { - while (i < mHistory.size()) { - ActivityRecord r = mHistory.get(i); - if (r.task.taskId != taskId) { - // Whoops hit the end. - return; - } - if (r.finishing) { - i++; - continue; - } - if (!finishActivityLocked(r, i, Activity.RESULT_CANCELED, - null, "clear", false)) { - i++; - } - } - } - - /** - * Completely remove all activities associated with an existing task. - */ - private final void performClearTaskLocked(int taskId) { - int i = mHistory.size(); - - // First find the requested task. - while (i > 0) { - i--; - ActivityRecord r = mHistory.get(i); - if (r.task.taskId == taskId) { - i++; - break; - } - } - - // Now find the start and clear it. - while (i > 0) { - i--; - ActivityRecord r = mHistory.get(i); - if (r.finishing) { - continue; - } - if (r.task.taskId != taskId) { - // We hit the bottom. Now finish it all... - performClearTaskAtIndexLocked(taskId, i+1); - return; - } - } - } - - /** - * Find the activity in the history stack within the given task. Returns - * the index within the history at which it's found, or < 0 if not found. - */ - private final int findActivityInHistoryLocked(ActivityRecord r, int task) { - int i = mHistory.size(); - while (i > 0) { - i--; - ActivityRecord candidate = mHistory.get(i); - if (candidate.finishing) { - continue; - } - if (candidate.task.taskId != task) { - break; - } - if (candidate.realActivity.equals(r.realActivity)) { - return i; - } - } - - return -1; - } - - /** - * Reorder the history stack so that the activity at the given index is - * brought to the front. - */ - private final ActivityRecord moveActivityToFrontLocked(int where) { - ActivityRecord newTop = mHistory.remove(where); - int top = mHistory.size(); - ActivityRecord oldTop = mHistory.get(top-1); - if (DEBUG_ADD_REMOVE) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.i(TAG, "Removing and adding activity " + newTop + " to stack at " - + top, here); - } - mHistory.add(top, newTop); - oldTop.frontOfTask = false; - newTop.frontOfTask = true; - return newTop; - } - - final int startActivityLocked(IApplicationThread caller, - Intent intent, String resolvedType, ActivityInfo aInfo, IBinder resultTo, - String resultWho, int requestCode, - int callingPid, int callingUid, String callingPackage, int startFlags, Bundle options, - boolean componentSpecified, ActivityRecord[] outActivity) { - - int err = ActivityManager.START_SUCCESS; - - ProcessRecord callerApp = null; - - if (caller != null) { - callerApp = mService.getRecordForAppLocked(caller); - if (callerApp != null) { - callingPid = callerApp.pid; - callingUid = callerApp.info.uid; - } else { - Slog.w(TAG, "Unable to find app for caller " + caller - + " (pid=" + callingPid + ") when starting: " - + intent.toString()); - err = ActivityManager.START_PERMISSION_DENIED; - } - } - - if (err == ActivityManager.START_SUCCESS) { - final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; - Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false) - + "} from pid " + (callerApp != null ? callerApp.pid : callingPid)); - } - - ActivityRecord sourceRecord = null; - ActivityRecord resultRecord = null; - if (resultTo != null) { - int index = indexOfTokenLocked(resultTo); - if (DEBUG_RESULTS) Slog.v( - TAG, "Will send result to " + resultTo + " (index " + index + ")"); - if (index >= 0) { - sourceRecord = mHistory.get(index); - if (requestCode >= 0 && !sourceRecord.finishing) { - resultRecord = sourceRecord; - } - } - } - - int launchFlags = intent.getFlags(); - - if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 - && sourceRecord != null) { - // Transfer the result target from the source activity to the new - // one being started, including any failures. - if (requestCode >= 0) { - ActivityOptions.abort(options); - return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; - } - resultRecord = sourceRecord.resultTo; - resultWho = sourceRecord.resultWho; - requestCode = sourceRecord.requestCode; - sourceRecord.resultTo = null; - if (resultRecord != null) { - resultRecord.removeResultsLocked( - sourceRecord, resultWho, requestCode); - } - } - - if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) { - // We couldn't find a class that can handle the given Intent. - // That's the end of that! - err = ActivityManager.START_INTENT_NOT_RESOLVED; - } - - if (err == ActivityManager.START_SUCCESS && aInfo == null) { - // We couldn't find the specific class specified in the Intent. - // Also the end of the line. - err = ActivityManager.START_CLASS_NOT_FOUND; - } - - if (err != ActivityManager.START_SUCCESS) { - if (resultRecord != null) { - sendActivityResultLocked(-1, - resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); - } - mDismissKeyguardOnNextActivity = false; - ActivityOptions.abort(options); - return err; - } - - final int startAnyPerm = mService.checkPermission( - START_ANY_ACTIVITY, callingPid, callingUid); - final int componentPerm = mService.checkComponentPermission(aInfo.permission, callingPid, - callingUid, aInfo.applicationInfo.uid, aInfo.exported); - if (startAnyPerm != PERMISSION_GRANTED && componentPerm != PERMISSION_GRANTED) { - if (resultRecord != null) { - sendActivityResultLocked(-1, - resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); - } - mDismissKeyguardOnNextActivity = false; - String msg; - if (!aInfo.exported) { - msg = "Permission Denial: starting " + intent.toString() - + " from " + callerApp + " (pid=" + callingPid - + ", uid=" + callingUid + ")" - + " not exported from uid " + aInfo.applicationInfo.uid; - } else { - msg = "Permission Denial: starting " + intent.toString() - + " from " + callerApp + " (pid=" + callingPid - + ", uid=" + callingUid + ")" - + " requires " + aInfo.permission; - } - Slog.w(TAG, msg); - throw new SecurityException(msg); - } - - boolean abort = !mService.mIntentFirewall.checkStartActivity(intent, - callerApp==null?null:callerApp.info, callingUid, callingPid, resolvedType, aInfo); - - if (mMainStack) { - if (mService.mController != null) { - try { - // The Intent we give to the watcher has the extra data - // stripped off, since it can contain private information. - Intent watchIntent = intent.cloneFilter(); - abort |= !mService.mController.activityStarting(watchIntent, - aInfo.applicationInfo.packageName); - } catch (RemoteException e) { - mService.mController = null; - } - } - } - - if (abort) { - if (resultRecord != null) { - sendActivityResultLocked(-1, - resultRecord, resultWho, requestCode, - Activity.RESULT_CANCELED, null); - } - // We pretend to the caller that it was really started, but - // they will just get a cancel result. - mDismissKeyguardOnNextActivity = false; - ActivityOptions.abort(options); - return ActivityManager.START_SUCCESS; - } - - ActivityRecord r = new ActivityRecord(mService, this, callerApp, callingUid, callingPackage, - intent, resolvedType, aInfo, mService.mConfiguration, - resultRecord, resultWho, requestCode, componentSpecified); - if (outActivity != null) { - outActivity[0] = r; - } - - if (mMainStack) { - if (mResumedActivity == null - || mResumedActivity.info.applicationInfo.uid != callingUid) { - if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) { - PendingActivityLaunch pal = new PendingActivityLaunch(); - pal.r = r; - pal.sourceRecord = sourceRecord; - pal.startFlags = startFlags; - mService.mPendingActivityLaunches.add(pal); - mDismissKeyguardOnNextActivity = false; - ActivityOptions.abort(options); - return ActivityManager.START_SWITCHES_CANCELED; - } - } - - if (mService.mDidAppSwitch) { - // This is the second allowed switch since we stopped switches, - // so now just generally allow switches. Use case: user presses - // home (switches disabled, switch to home, mDidAppSwitch now true); - // user taps a home icon (coming from home so allowed, we hit here - // and now allow anyone to switch again). - mService.mAppSwitchesAllowedTime = 0; - } else { - mService.mDidAppSwitch = true; - } - - mService.doPendingActivityLaunchesLocked(false); - } - - err = startActivityUncheckedLocked(r, sourceRecord, - startFlags, true, options); - if (mDismissKeyguardOnNextActivity && mPausingActivity == null) { - // Someone asked to have the keyguard dismissed on the next - // activity start, but we are not actually doing an activity - // switch... just dismiss the keyguard now, because we - // probably want to see whatever is behind it. - mDismissKeyguardOnNextActivity = false; - mService.mWindowManager.dismissKeyguard(); - } - return err; - } - - final void moveHomeToFrontFromLaunchLocked(int launchFlags) { - if ((launchFlags & - (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) - == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) { - // Caller wants to appear on home activity, so before starting - // their own activity we will bring home to the front. - moveHomeToFrontLocked(); } + return taskInsertionPoint; } - final int startActivityUncheckedLocked(ActivityRecord r, - ActivityRecord sourceRecord, int startFlags, boolean doResume, - Bundle options) { - final Intent intent = r.intent; - final int callingUid = r.launchedFromUid; - - int launchFlags = intent.getFlags(); - - // We'll invoke onUserLeaving before onPause only if the launching - // activity did not explicitly state that this is an automated launch. - mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; - if (DEBUG_USER_LEAVING) Slog.v(TAG, - "startActivity() => mUserLeaving=" + mUserLeaving); - - // If the caller has asked not to resume at this point, we make note - // of this in the record so that we can skip it when trying to find - // the top running activity. - if (!doResume) { - r.delayedResume = true; - } - - ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) - != 0 ? r : null; - - // If the onlyIfNeeded flag is set, then we can do this if the activity - // being launched is the same as the one making the call... or, as - // a special case, if we do not know the caller then we count the - // current top activity as the caller. - if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { - ActivityRecord checkedCaller = sourceRecord; - if (checkedCaller == null) { - checkedCaller = topRunningNonDelayedActivityLocked(notTop); - } - if (!checkedCaller.realActivity.equals(r.realActivity)) { - // Caller is not the same as launcher, so always needed. - startFlags &= ~ActivityManager.START_FLAG_ONLY_IF_NEEDED; + final ActivityRecord resetTaskIfNeededLocked(ActivityRecord taskTop, + ActivityRecord newActivity) { + boolean forceReset = + (newActivity.info.flags & ActivityInfo.FLAG_CLEAR_TASK_ON_LAUNCH) != 0; + if (ACTIVITY_INACTIVE_RESET_TIME > 0 + && taskTop.task.getInactiveDuration() > ACTIVITY_INACTIVE_RESET_TIME) { + if ((newActivity.info.flags & ActivityInfo.FLAG_ALWAYS_RETAIN_TASK_STATE) == 0) { + forceReset = true; } } - if (sourceRecord == null) { - // This activity is not being started from another... in this - // case we -always- start a new task. - if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { - Slog.w(TAG, "startActivity called from non-Activity context; forcing Intent.FLAG_ACTIVITY_NEW_TASK for: " - + intent); - launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; - } - } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { - // The original activity who is starting us is running as a single - // instance... this new activity it is starting must go on its - // own task. - launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; - } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { - // The activity being started is a single instance... it always - // gets launched into its own task. - launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; - } - - if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { - // For whatever reason this activity is being launched into a new - // task... yet the caller has requested a result back. Well, that - // is pretty messed up, so instead immediately send back a cancel - // and let the new task continue launched as normal without a - // dependency on its originator. - Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result."); - sendActivityResultLocked(-1, - r.resultTo, r.resultWho, r.requestCode, - Activity.RESULT_CANCELED, null); - r.resultTo = null; - } + final TaskRecord task = taskTop.task; - boolean addingToTask = false; - boolean movedHome = false; - TaskRecord reuseTask = null; - if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && - (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { - // If bring to front is requested, and no result is requested, and - // we can find a task that was started with this same - // component, then instead of launching bring that one to the front. - if (r.resultTo == null) { - // See if there is a task to bring to the front. If this is - // a SINGLE_INSTANCE activity, there can be one and only one - // instance of it in the history, and it is always in its own - // unique task, so we do a special search. - ActivityRecord taskTop = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE - ? findTaskLocked(intent, r.info) - : findActivityLocked(intent, r.info); - if (taskTop != null) { - if (taskTop.task.intent == null) { - // This task was started because of movement of - // the activity based on affinity... now that we - // are actually launching it, we can assign the - // base intent. - taskTop.task.setIntent(intent, r.info); - } - // If the target task is not in the front, then we need - // to bring it to the front... except... well, with - // SINGLE_TASK_LAUNCH it's not entirely clear. We'd like - // to have the same behavior as if a new instance was - // being started, which means not bringing it to the front - // if the caller is not itself in the front. - ActivityRecord curTop = topRunningNonDelayedActivityLocked(notTop); - if (curTop != null && curTop.task != taskTop.task) { - r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); - boolean callerAtFront = sourceRecord == null - || curTop.task == sourceRecord.task; - if (callerAtFront) { - // We really do want to push this one into the - // user's face, right now. - movedHome = true; - moveHomeToFrontFromLaunchLocked(launchFlags); - moveTaskToFrontLocked(taskTop.task, r, options); - options = null; - } - } - // If the caller has requested that the target task be - // reset, then do so. - if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { - taskTop = resetTaskIfNeededLocked(taskTop, r); - } - if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { - // We don't need to start a new activity, and - // the client said not to do anything if that - // is the case, so this is it! And for paranoia, make - // sure we have correctly resumed the top activity. - if (doResume) { - resumeTopActivityLocked(null, options); - } else { - ActivityOptions.abort(options); - } - return ActivityManager.START_RETURN_INTENT_TO_CALLER; - } - if ((launchFlags & - (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) - == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) { - // The caller has requested to completely replace any - // existing task with its new activity. Well that should - // not be too hard... - reuseTask = taskTop.task; - performClearTaskLocked(taskTop.task.taskId); - reuseTask.setIntent(r.intent, r.info); - } else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { - // In this situation we want to remove all activities - // from the task up to the one being started. In most - // cases this means we are resetting the task to its - // initial state. - ActivityRecord top = performClearTaskLocked( - taskTop.task.taskId, r, launchFlags); - if (top != null) { - if (top.frontOfTask) { - // Activity aliases may mean we use different - // intents for the top activity, so make sure - // the task now has the identity of the new - // intent. - top.task.setIntent(r.intent, r.info); - } - logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); - top.deliverNewIntentLocked(callingUid, r.intent); - } else { - // A special case: we need to - // start the activity because it is not currently - // running, and the caller has asked to clear the - // current task to have this activity at the top. - addingToTask = true; - // Now pretend like this activity is being started - // by the top of its task, so it is put in the - // right place. - sourceRecord = taskTop; - } - } else if (r.realActivity.equals(taskTop.task.realActivity)) { - // In this case the top activity on the task is the - // same as the one being launched, so we take that - // as a request to bring the task to the foreground. - // If the top activity in the task is the root - // activity, deliver this new intent to it if it - // desires. - if (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) - && taskTop.realActivity.equals(r.realActivity)) { - logStartActivity(EventLogTags.AM_NEW_INTENT, r, taskTop.task); - if (taskTop.frontOfTask) { - taskTop.task.setIntent(r.intent, r.info); - } - taskTop.deliverNewIntentLocked(callingUid, r.intent); - } else if (!r.intent.filterEquals(taskTop.task.intent)) { - // In this case we are launching the root activity - // of the task, but with a different intent. We - // should start a new instance on top. - addingToTask = true; - sourceRecord = taskTop; - } - } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) { - // In this case an activity is being launched in to an - // existing task, without resetting that task. This - // is typically the situation of launching an activity - // from a notification or shortcut. We want to place - // the new activity on top of the current task. - addingToTask = true; - sourceRecord = taskTop; - } else if (!taskTop.task.rootWasReset) { - // In this case we are launching in to an existing task - // that has not yet been started from its front door. - // The current task has been brought to the front. - // Ideally, we'd probably like to place this new task - // at the bottom of its stack, but that's a little hard - // to do with the current organization of the code so - // for now we'll just drop it. - taskTop.task.setIntent(r.intent, r.info); - } - if (!addingToTask && reuseTask == null) { - // We didn't do anything... but it was needed (a.k.a., client - // don't use that intent!) And for paranoia, make - // sure we have correctly resumed the top activity. - if (doResume) { - resumeTopActivityLocked(null, options); - } else { - ActivityOptions.abort(options); - } - return ActivityManager.START_TASK_TO_FRONT; - } - } - } - } + /** False until we evaluate the TaskRecord associated with taskTop. Switches to true + * for remaining tasks. Used for later tasks to reparent to task. */ + boolean taskFound = false; - //String uri = r.intent.toURI(); - //Intent intent2 = new Intent(uri); - //Slog.i(TAG, "Given intent: " + r.intent); - //Slog.i(TAG, "URI is: " + uri); - //Slog.i(TAG, "To intent: " + intent2); - - if (r.packageName != null) { - // If the activity being launched is the same as the one currently - // at the top, then we need to check if it should only be launched - // once. - ActivityRecord top = topRunningNonDelayedActivityLocked(notTop); - if (top != null && r.resultTo == null) { - if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) { - if (top.app != null && top.app.thread != null) { - if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP - || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { - logStartActivity(EventLogTags.AM_NEW_INTENT, top, top.task); - // For paranoia, make sure we have correctly - // resumed the top activity. - if (doResume) { - resumeTopActivityLocked(null); - } - ActivityOptions.abort(options); - if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { - // We don't need to start a new activity, and - // the client said not to do anything if that - // is the case, so this is it! - return ActivityManager.START_RETURN_INTENT_TO_CALLER; - } - top.deliverNewIntentLocked(callingUid, r.intent); - return ActivityManager.START_DELIVERED_TO_TOP; - } - } - } - } + /** If ActivityOptions are moved out and need to be aborted or moved to taskTop. */ + ActivityOptions topOptions = null; - } else { - if (r.resultTo != null) { - sendActivityResultLocked(-1, - r.resultTo, r.resultWho, r.requestCode, - Activity.RESULT_CANCELED, null); - } - ActivityOptions.abort(options); - return ActivityManager.START_CLASS_NOT_FOUND; - } + // Preserve the location for reparenting in the new task. + int reparentInsertionPoint = -1; - boolean newTask = false; - boolean keepCurTransition = false; + for (int i = mTaskHistory.size() - 1; i >= 0; --i) { + final TaskRecord targetTask = mTaskHistory.get(i); - // Should this be considered a new task? - if (r.resultTo == null && !addingToTask - && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { - if (reuseTask == null) { - // todo: should do better management of integers. - mService.mCurTask++; - if (mService.mCurTask <= 0) { - mService.mCurTask = 1; - } - r.setTask(new TaskRecord(mService.mCurTask, r.info, intent), null, true); - if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r - + " in new task " + r.task); + if (targetTask == task) { + topOptions = resetTargetTaskIfNeededLocked(task, forceReset); + taskFound = true; } else { - r.setTask(reuseTask, reuseTask, true); + reparentInsertionPoint = resetAffinityTaskIfNeededLocked(targetTask, task, + taskFound, forceReset, reparentInsertionPoint); } - newTask = true; - if (!movedHome) { - moveHomeToFrontFromLaunchLocked(launchFlags); - } - - } else if (sourceRecord != null) { - if (!addingToTask && - (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { - // In this case, we are adding the activity to an existing - // task, but the caller has asked to clear that task if the - // activity is already running. - ActivityRecord top = performClearTaskLocked( - sourceRecord.task.taskId, r, launchFlags); - keepCurTransition = true; - if (top != null) { - logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); - top.deliverNewIntentLocked(callingUid, r.intent); - // For paranoia, make sure we have correctly - // resumed the top activity. - if (doResume) { - resumeTopActivityLocked(null); - } - ActivityOptions.abort(options); - return ActivityManager.START_DELIVERED_TO_TOP; - } - } else if (!addingToTask && - (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) { - // In this case, we are launching an activity in our own task - // that may already be running somewhere in the history, and - // we want to shuffle it to the front of the stack if so. - int where = findActivityInHistoryLocked(r, sourceRecord.task.taskId); - if (where >= 0) { - ActivityRecord top = moveActivityToFrontLocked(where); - logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); - top.updateOptionsLocked(options); - top.deliverNewIntentLocked(callingUid, r.intent); - if (doResume) { - resumeTopActivityLocked(null); - } - return ActivityManager.START_DELIVERED_TO_TOP; - } - } - // An existing activity is starting this new activity, so we want - // to keep the new one in the same task as the one that is starting - // it. - r.setTask(sourceRecord.task, sourceRecord.thumbHolder, false); - if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r - + " in existing task " + r.task); - - } else { - // This not being started from an existing activity, and not part - // of a new task... just put it in the top task, though these days - // this case should never happen. - final int N = mHistory.size(); - ActivityRecord prev = - N > 0 ? mHistory.get(N-1) : null; - r.setTask(prev != null - ? prev.task - : new TaskRecord(mService.mCurTask, r.info, intent), null, true); - if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r - + " in new guessed " + r.task); - } - - mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, - intent, r.getUriPermissionsLocked()); - - if (newTask) { - EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId); - } - logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task); - startActivityLocked(r, newTask, doResume, keepCurTransition, options); - return ActivityManager.START_SUCCESS; - } - - ActivityInfo resolveActivity(Intent intent, String resolvedType, int startFlags, - String profileFile, ParcelFileDescriptor profileFd, int userId) { - // Collect information about the target of the Intent. - ActivityInfo aInfo; - try { - ResolveInfo rInfo = - AppGlobals.getPackageManager().resolveIntent( - intent, resolvedType, - PackageManager.MATCH_DEFAULT_ONLY - | ActivityManagerService.STOCK_PM_FLAGS, userId); - aInfo = rInfo != null ? rInfo.activityInfo : null; - } catch (RemoteException e) { - aInfo = null; } - if (aInfo != null) { - // Store the found target back into the intent, because now that - // we have it we never want to do this again. For example, if the - // user navigates back to this point in the history, we should - // always restart the exact same activity. - intent.setComponent(new ComponentName( - aInfo.applicationInfo.packageName, aInfo.name)); - - // Don't debug things in the system process - if ((startFlags&ActivityManager.START_FLAG_DEBUG) != 0) { - if (!aInfo.processName.equals("system")) { - mService.setDebugApp(aInfo.processName, true, false); - } - } - - if ((startFlags&ActivityManager.START_FLAG_OPENGL_TRACES) != 0) { - if (!aInfo.processName.equals("system")) { - mService.setOpenGlTraceApp(aInfo.applicationInfo, aInfo.processName); - } - } - - if (profileFile != null) { - if (!aInfo.processName.equals("system")) { - mService.setProfileApp(aInfo.applicationInfo, aInfo.processName, - profileFile, profileFd, - (startFlags&ActivityManager.START_FLAG_AUTO_STOP_PROFILER) != 0); - } - } - } - return aInfo; - } + int taskNdx = mTaskHistory.indexOf(task); + do { + taskTop = mTaskHistory.get(taskNdx--).getTopActivity(); + } while (taskTop == null && taskNdx >= 0); - final int startActivityMayWait(IApplicationThread caller, int callingUid, - String callingPackage, Intent intent, String resolvedType, IBinder resultTo, - String resultWho, int requestCode, int startFlags, String profileFile, - ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config, - Bundle options, int userId) { - // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - boolean componentSpecified = intent.getComponent() != null; - - // Don't modify the client's object! - intent = new Intent(intent); - - // Collect information about the target of the Intent. - ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags, - profileFile, profileFd, userId); - - synchronized (mService) { - int callingPid; - if (callingUid >= 0) { - callingPid = -1; - } else if (caller == null) { - callingPid = Binder.getCallingPid(); - callingUid = Binder.getCallingUid(); + if (topOptions != null) { + // If we got some ActivityOptions from an activity on top that + // was removed from the task, propagate them to the new real top. + if (taskTop != null) { + taskTop.updateOptionsLocked(topOptions); } else { - callingPid = callingUid = -1; - } - - mConfigWillChange = config != null - && mService.mConfiguration.diff(config) != 0; - if (DEBUG_CONFIGURATION) Slog.v(TAG, - "Starting activity when config will change = " + mConfigWillChange); - - final long origId = Binder.clearCallingIdentity(); - - if (mMainStack && aInfo != null && - (aInfo.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { - // This may be a heavy-weight process! Check to see if we already - // have another, different heavy-weight process running. - if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) { - if (mService.mHeavyWeightProcess != null && - (mService.mHeavyWeightProcess.info.uid != aInfo.applicationInfo.uid || - !mService.mHeavyWeightProcess.processName.equals(aInfo.processName))) { - int realCallingPid = callingPid; - int realCallingUid = callingUid; - if (caller != null) { - ProcessRecord callerApp = mService.getRecordForAppLocked(caller); - if (callerApp != null) { - realCallingPid = callerApp.pid; - realCallingUid = callerApp.info.uid; - } else { - Slog.w(TAG, "Unable to find app for caller " + caller - + " (pid=" + realCallingPid + ") when starting: " - + intent.toString()); - ActivityOptions.abort(options); - return ActivityManager.START_PERMISSION_DENIED; - } - } - - IIntentSender target = mService.getIntentSenderLocked( - ActivityManager.INTENT_SENDER_ACTIVITY, "android", - realCallingUid, userId, null, null, 0, new Intent[] { intent }, - new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT - | PendingIntent.FLAG_ONE_SHOT, null); - - Intent newIntent = new Intent(); - if (requestCode >= 0) { - // Caller is requesting a result. - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true); - } - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, - new IntentSender(target)); - if (mService.mHeavyWeightProcess.activities.size() > 0) { - ActivityRecord hist = mService.mHeavyWeightProcess.activities.get(0); - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP, - hist.packageName); - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK, - hist.task.taskId); - } - newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP, - aInfo.packageName); - newIntent.setFlags(intent.getFlags()); - newIntent.setClassName("android", - HeavyWeightSwitcherActivity.class.getName()); - intent = newIntent; - resolvedType = null; - caller = null; - callingUid = Binder.getCallingUid(); - callingPid = Binder.getCallingPid(); - componentSpecified = true; - try { - ResolveInfo rInfo = - AppGlobals.getPackageManager().resolveIntent( - intent, null, - PackageManager.MATCH_DEFAULT_ONLY - | ActivityManagerService.STOCK_PM_FLAGS, userId); - aInfo = rInfo != null ? rInfo.activityInfo : null; - aInfo = mService.getActivityInfoForUser(aInfo, userId); - } catch (RemoteException e) { - aInfo = null; - } - } - } - } - - int res = startActivityLocked(caller, intent, resolvedType, - aInfo, resultTo, resultWho, requestCode, callingPid, callingUid, - callingPackage, startFlags, options, componentSpecified, null); - - if (mConfigWillChange && mMainStack) { - // If the caller also wants to switch to a new configuration, - // do so now. This allows a clean switch, as we are waiting - // for the current activity to pause (so we will not destroy - // it), and have not yet started the next activity. - mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, - "updateConfiguration()"); - mConfigWillChange = false; - if (DEBUG_CONFIGURATION) Slog.v(TAG, - "Updating to new configuration after starting activity."); - mService.updateConfigurationLocked(config, null, false, false); - } - - Binder.restoreCallingIdentity(origId); - - if (outResult != null) { - outResult.result = res; - if (res == ActivityManager.START_SUCCESS) { - mWaitingActivityLaunched.add(outResult); - do { - try { - mService.wait(); - } catch (InterruptedException e) { - } - } while (!outResult.timeout && outResult.who == null); - } else if (res == ActivityManager.START_TASK_TO_FRONT) { - ActivityRecord r = this.topRunningActivityLocked(null); - if (r.nowVisible) { - outResult.timeout = false; - outResult.who = new ComponentName(r.info.packageName, r.info.name); - outResult.totalTime = 0; - outResult.thisTime = 0; - } else { - outResult.thisTime = SystemClock.uptimeMillis(); - mWaitingActivityVisible.add(outResult); - do { - try { - mService.wait(); - } catch (InterruptedException e) { - } - } while (!outResult.timeout && outResult.who == null); - } - } - } - - return res; - } - } - - final int startActivities(IApplicationThread caller, int callingUid, String callingPackage, - Intent[] intents, String[] resolvedTypes, IBinder resultTo, - Bundle options, int userId) { - if (intents == null) { - throw new NullPointerException("intents is null"); - } - if (resolvedTypes == null) { - throw new NullPointerException("resolvedTypes is null"); - } - if (intents.length != resolvedTypes.length) { - throw new IllegalArgumentException("intents are length different than resolvedTypes"); - } - - ActivityRecord[] outActivity = new ActivityRecord[1]; - - int callingPid; - if (callingUid >= 0) { - callingPid = -1; - } else if (caller == null) { - callingPid = Binder.getCallingPid(); - callingUid = Binder.getCallingUid(); - } else { - callingPid = callingUid = -1; - } - final long origId = Binder.clearCallingIdentity(); - try { - synchronized (mService) { - - for (int i=0; i<intents.length; i++) { - Intent intent = intents[i]; - if (intent == null) { - continue; - } - - // Refuse possible leaked file descriptors - if (intent != null && intent.hasFileDescriptors()) { - throw new IllegalArgumentException("File descriptors passed in Intent"); - } - - boolean componentSpecified = intent.getComponent() != null; - - // Don't modify the client's object! - intent = new Intent(intent); - - // Collect information about the target of the Intent. - ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], - 0, null, null, userId); - // TODO: New, check if this is correct - aInfo = mService.getActivityInfoForUser(aInfo, userId); - - if (mMainStack && aInfo != null && (aInfo.applicationInfo.flags - & ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { - throw new IllegalArgumentException( - "FLAG_CANT_SAVE_STATE not supported here"); - } - - Bundle theseOptions; - if (options != null && i == intents.length-1) { - theseOptions = options; - } else { - theseOptions = null; - } - int res = startActivityLocked(caller, intent, resolvedTypes[i], - aInfo, resultTo, null, -1, callingPid, callingUid, callingPackage, - 0, theseOptions, componentSpecified, outActivity); - if (res < 0) { - return res; - } - - resultTo = outActivity[0] != null ? outActivity[0].appToken : null; - } - } - } finally { - Binder.restoreCallingIdentity(origId); - } - - return ActivityManager.START_SUCCESS; - } - - void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, - long thisTime, long totalTime) { - for (int i=mWaitingActivityLaunched.size()-1; i>=0; i--) { - WaitResult w = mWaitingActivityLaunched.get(i); - w.timeout = timeout; - if (r != null) { - w.who = new ComponentName(r.info.packageName, r.info.name); - } - w.thisTime = thisTime; - w.totalTime = totalTime; - } - mService.notifyAll(); - } - - void reportActivityVisibleLocked(ActivityRecord r) { - for (int i=mWaitingActivityVisible.size()-1; i>=0; i--) { - WaitResult w = mWaitingActivityVisible.get(i); - w.timeout = false; - if (r != null) { - w.who = new ComponentName(r.info.packageName, r.info.name); + topOptions.abort(); } - w.totalTime = SystemClock.uptimeMillis() - w.thisTime; - w.thisTime = w.totalTime; } - mService.notifyAll(); - if (mDismissKeyguardOnNextActivity) { - mDismissKeyguardOnNextActivity = false; - mService.mWindowManager.dismissKeyguard(); - } + return taskTop; } void sendActivityResultLocked(int callingUid, ActivityRecord r, @@ -3394,7 +2174,7 @@ final class ActivityStack { r.addResultLocked(null, resultWho, requestCode, resultCode, data); } - private final void stopActivityLocked(ActivityRecord r) { + final void stopActivityLocked(ActivityRecord r) { if (DEBUG_SWITCH) Slog.d(TAG, "Stopping: " + r); if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_HISTORY) != 0 || (r.info.flags&ActivityInfo.FLAG_NO_HISTORY) != 0) { @@ -3413,7 +2193,7 @@ final class ActivityStack { } if (r.app != null && r.app.thread != null) { - if (mMainStack) { + if (mStackSupervisor.isFrontStack(this)) { if (mService.mFocusedActivity == r) { mService.setFocusedActivityLocked(topRunningActivityLocked(null)); } @@ -3427,14 +2207,13 @@ final class ActivityStack { if (DEBUG_VISBILITY) Slog.v( TAG, "Stopping visible=" + r.visible + " for " + r); if (!r.visible) { - mService.mWindowManager.setAppVisibility(r.appToken, false); + mWindowManager.setAppVisibility(r.appToken, false); } r.app.thread.scheduleStopActivity(r.appToken, r.visible, r.configChangeFlags); - if (mService.isSleeping()) { + if (mService.isSleepingOrShuttingDown()) { r.setSleeping(true); } - Message msg = mHandler.obtainMessage(STOP_TIMEOUT_MSG); - msg.obj = r; + Message msg = mHandler.obtainMessage(STOP_TIMEOUT_MSG, r); mHandler.sendMessageDelayed(msg, STOP_TIMEOUT); } catch (Exception e) { // Maybe just ignore exceptions here... if the process @@ -3451,217 +2230,6 @@ final class ActivityStack { } } } - - final ArrayList<ActivityRecord> processStoppingActivitiesLocked( - boolean remove) { - int N = mStoppingActivities.size(); - if (N <= 0) return null; - - ArrayList<ActivityRecord> stops = null; - - final boolean nowVisible = mResumedActivity != null - && mResumedActivity.nowVisible - && !mResumedActivity.waitingVisible; - for (int i=0; i<N; i++) { - ActivityRecord s = mStoppingActivities.get(i); - if (localLOGV) Slog.v(TAG, "Stopping " + s + ": nowVisible=" - + nowVisible + " waitingVisible=" + s.waitingVisible - + " finishing=" + s.finishing); - if (s.waitingVisible && nowVisible) { - mWaitingVisibleActivities.remove(s); - s.waitingVisible = false; - if (s.finishing) { - // If this activity is finishing, it is sitting on top of - // everyone else but we now know it is no longer needed... - // so get rid of it. Otherwise, we need to go through the - // normal flow and hide it once we determine that it is - // hidden by the activities in front of it. - if (localLOGV) Slog.v(TAG, "Before stopping, can hide: " + s); - mService.mWindowManager.setAppVisibility(s.appToken, false); - } - } - if ((!s.waitingVisible || mService.isSleeping()) && remove) { - if (localLOGV) Slog.v(TAG, "Ready to stop: " + s); - if (stops == null) { - stops = new ArrayList<ActivityRecord>(); - } - stops.add(s); - mStoppingActivities.remove(i); - N--; - i--; - } - } - - return stops; - } - - final void scheduleIdleLocked() { - Message msg = Message.obtain(); - msg.what = IDLE_NOW_MSG; - mHandler.sendMessage(msg); - } - - final ActivityRecord activityIdleInternal(IBinder token, boolean fromTimeout, - Configuration config) { - if (localLOGV) Slog.v(TAG, "Activity idle: " + token); - - ActivityRecord res = null; - - ArrayList<ActivityRecord> stops = null; - ArrayList<ActivityRecord> finishes = null; - ArrayList<ActivityRecord> thumbnails = null; - ArrayList<UserStartedState> startingUsers = null; - int NS = 0; - int NF = 0; - int NT = 0; - IApplicationThread sendThumbnail = null; - boolean booting = false; - boolean enableScreen = false; - boolean activityRemoved = false; - - synchronized (mService) { - ActivityRecord r = ActivityRecord.forToken(token); - if (r != null) { - mHandler.removeMessages(IDLE_TIMEOUT_MSG, r); - r.finishLaunchTickingLocked(); - } - - // Get the activity record. - int index = indexOfActivityLocked(r); - if (index >= 0) { - res = r; - - if (fromTimeout) { - reportActivityLaunchedLocked(fromTimeout, r, -1, -1); - } - - // 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); - mLaunchingActivity.release(); - } - - // We are now idle. If someone is waiting for a thumbnail from - // us, we can now deliver. - r.idle = true; - mService.scheduleAppGcsLocked(); - if (r.thumbnailNeeded && r.app != null && r.app.thread != null) { - sendThumbnail = r.app.thread; - r.thumbnailNeeded = false; - } - - // If this activity is fullscreen, set up to hide those under it. - - if (DEBUG_VISBILITY) Slog.v(TAG, "Idle activity for " + r); - ensureActivitiesVisibleLocked(null, 0); - - //Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout); - if (mMainStack) { - if (!mService.mBooted) { - mService.mBooted = true; - enableScreen = true; - } - } - - } else if (fromTimeout) { - reportActivityLaunchedLocked(fromTimeout, null, -1, -1); - } - - // Atomically retrieve all of the other things to do. - stops = processStoppingActivitiesLocked(true); - NS = stops != null ? stops.size() : 0; - if ((NF=mFinishingActivities.size()) > 0) { - finishes = new ArrayList<ActivityRecord>(mFinishingActivities); - mFinishingActivities.clear(); - } - if ((NT=mService.mCancelledThumbnails.size()) > 0) { - thumbnails = new ArrayList<ActivityRecord>(mService.mCancelledThumbnails); - mService.mCancelledThumbnails.clear(); - } - - if (mMainStack) { - booting = mService.mBooting; - mService.mBooting = false; - } - if (mStartingUsers.size() > 0) { - startingUsers = new ArrayList<UserStartedState>(mStartingUsers); - mStartingUsers.clear(); - } - } - - int i; - - // Send thumbnail if requested. - if (sendThumbnail != null) { - try { - sendThumbnail.requestThumbnail(token); - } catch (Exception e) { - Slog.w(TAG, "Exception thrown when requesting thumbnail", e); - mService.sendPendingThumbnail(null, token, null, null, true); - } - } - - // Stop any activities that are scheduled to do so but have been - // waiting for the next one to start. - for (i=0; i<NS; i++) { - ActivityRecord r = (ActivityRecord)stops.get(i); - synchronized (mService) { - if (r.finishing) { - finishCurrentActivityLocked(r, FINISH_IMMEDIATELY, false); - } else { - stopActivityLocked(r); - } - } - } - - // Finish any activities that are scheduled to do so but have been - // waiting for the next one to start. - for (i=0; i<NF; i++) { - ActivityRecord r = (ActivityRecord)finishes.get(i); - synchronized (mService) { - activityRemoved = destroyActivityLocked(r, true, false, "finish-idle"); - } - } - - // Report back to any thumbnail receivers. - for (i=0; i<NT; i++) { - ActivityRecord r = (ActivityRecord)thumbnails.get(i); - mService.sendPendingThumbnail(r, null, null, null, true); - } - - if (booting) { - mService.finishBooting(); - } else if (startingUsers != null) { - for (i=0; i<startingUsers.size(); i++) { - mService.finishUserSwitch(startingUsers.get(i)); - } - } - - mService.trimApplications(); - //dump(); - //mWindowManager.dump(); - - if (enableScreen) { - mService.enableScreenAfterBoot(); - } - - if (activityRemoved) { - synchronized (mService) { - resumeTopActivityLocked(null); - } - } - - return res; - } /** * @return Returns true if the activity is being finished, false if for @@ -3669,63 +2237,82 @@ final class ActivityStack { */ final boolean requestFinishActivityLocked(IBinder token, int resultCode, Intent resultData, String reason, boolean oomAdj) { - int index = indexOfTokenLocked(token); + ActivityRecord r = isInStackLocked(token); if (DEBUG_RESULTS || DEBUG_STATES) Slog.v( - TAG, "Finishing activity @" + index + ": token=" + token + TAG, "Finishing activity token=" + token + " r=" + ", result=" + resultCode + ", data=" + resultData + ", reason=" + reason); - if (index < 0) { + if (r == null) { return false; } - ActivityRecord r = mHistory.get(index); - finishActivityLocked(r, index, resultCode, resultData, reason, oomAdj); + finishActivityLocked(r, resultCode, resultData, reason, oomAdj); return true; } - final void finishSubActivityLocked(IBinder token, String resultWho, int requestCode) { - ActivityRecord self = isInStackLocked(token); - if (self == null) { - return; - } - - int i; - for (i=mHistory.size()-1; i>=0; i--) { - ActivityRecord r = (ActivityRecord)mHistory.get(i); - if (r.resultTo == self && r.requestCode == requestCode) { - if ((r.resultWho == null && resultWho == null) || - (r.resultWho != null && r.resultWho.equals(resultWho))) { - finishActivityLocked(r, i, - Activity.RESULT_CANCELED, null, "request-sub", false); + final void finishSubActivityLocked(ActivityRecord self, String resultWho, int requestCode) { + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + ActivityRecord r = activities.get(activityNdx); + if (r.resultTo == self && r.requestCode == requestCode) { + if ((r.resultWho == null && resultWho == null) || + (r.resultWho != null && r.resultWho.equals(resultWho))) { + finishActivityLocked(r, Activity.RESULT_CANCELED, null, "request-sub", + false); + } } } } mService.updateOomAdjLocked(); } - final boolean finishActivityAffinityLocked(IBinder token) { - int index = indexOfTokenLocked(token); - if (DEBUG_RESULTS) Slog.v( - TAG, "Finishing activity affinity @" + index + ": token=" + token); - if (index < 0) { - return false; + final void finishTopRunningActivityLocked(ProcessRecord app) { + ActivityRecord r = topRunningActivityLocked(null); + if (r != null && r.app == app) { + // If the top running activity is from this crashing + // process, then terminate it to avoid getting in a loop. + Slog.w(TAG, " Force finishing activity " + + r.intent.getComponent().flattenToShortString()); + int taskNdx = mTaskHistory.indexOf(r.task); + int activityNdx = r.task.mActivities.indexOf(r); + finishActivityLocked(r, Activity.RESULT_CANCELED, null, "crashed", false); + // Also terminate any activities below it that aren't yet + // stopped, to avoid a situation where one will get + // re-start our crashing activity once it gets resumed again. + --activityNdx; + if (activityNdx < 0) { + do { + --taskNdx; + if (taskNdx < 0) { + break; + } + activityNdx = mTaskHistory.get(taskNdx).mActivities.size() - 1; + } while (activityNdx < 0); + } + if (activityNdx >= 0) { + r = mTaskHistory.get(taskNdx).mActivities.get(activityNdx); + if (r.state == ActivityState.RESUMED + || r.state == ActivityState.PAUSING + || r.state == ActivityState.PAUSED) { + if (!r.isHomeActivity() || mService.mHomeProcess != r.app) { + Slog.w(TAG, " Force finishing activity " + + r.intent.getComponent().flattenToShortString()); + finishActivityLocked(r, Activity.RESULT_CANCELED, null, "crashed", false); + } + } + } } - ActivityRecord r = mHistory.get(index); + } - while (index >= 0) { - ActivityRecord cur = mHistory.get(index); - if (cur.task != r.task) { - break; - } - if (cur.taskAffinity == null && r.taskAffinity != null) { + final boolean finishActivityAffinityLocked(ActivityRecord r) { + ArrayList<ActivityRecord> activities = r.task.mActivities; + for (int index = activities.indexOf(r); index >= 0; --index) { + ActivityRecord cur = activities.get(index); + if (!Objects.equal(cur.taskAffinity, r.taskAffinity)) { break; } - if (cur.taskAffinity != null && !cur.taskAffinity.equals(r.taskAffinity)) { - break; - } - finishActivityLocked(cur, index, Activity.RESULT_CANCELED, null, - "request-affinity", true); - index--; + finishActivityLocked(cur, Activity.RESULT_CANCELED, null, "request-affinity", true); } return true; } @@ -3761,17 +2348,8 @@ final class ActivityStack { * @return Returns true if this activity has been removed from the history * list, or false if it is still in the list and will be removed later. */ - final boolean finishActivityLocked(ActivityRecord r, int index, - int resultCode, Intent resultData, String reason, boolean oomAdj) { - return finishActivityLocked(r, index, resultCode, resultData, reason, false, oomAdj); - } - - /** - * @return Returns true if this activity has been removed from the history - * list, or false if it is still in the list and will be removed later. - */ - final boolean finishActivityLocked(ActivityRecord r, int index, int resultCode, - Intent resultData, String reason, boolean immediate, boolean oomAdj) { + final boolean finishActivityLocked(ActivityRecord r, int resultCode, Intent resultData, + String reason, boolean oomAdj) { if (r.finishing) { Slog.w(TAG, "Duplicate finish request for " + r); return false; @@ -3781,53 +2359,49 @@ final class ActivityStack { EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY, r.userId, System.identityHashCode(r), r.task.taskId, r.shortComponentName, reason); - if (index < (mHistory.size()-1)) { - ActivityRecord next = mHistory.get(index+1); - if (next.task == r.task) { - if (r.frontOfTask) { - // The next activity is now the front of the task. - next.frontOfTask = true; - } - if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { - // If the caller asked that this activity (and all above it) - // be cleared when the task is reset, don't lose that information, - // but propagate it up to the next activity. - next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); - } + final ArrayList<ActivityRecord> activities = r.task.mActivities; + final int index = activities.indexOf(r); + if (index < (activities.size() - 1)) { + ActivityRecord next = activities.get(index+1); + if (r.frontOfTask) { + // The next activity is now the front of the task. + next.frontOfTask = true; + } + if ((r.intent.getFlags()&Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET) != 0) { + // If the caller asked that this activity (and all above it) + // be cleared when the task is reset, don't lose that information, + // but propagate it up to the next activity. + next.intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET); } } r.pauseKeyDispatchingLocked(); - if (mMainStack) { + if (mStackSupervisor.isFrontStack(this)) { if (mService.mFocusedActivity == r) { - mService.setFocusedActivityLocked(topRunningActivityLocked(null)); + mService.setFocusedActivityLocked(mStackSupervisor.topRunningActivityLocked()); } } finishActivityResultsLocked(r, resultCode, resultData); - - if (mService.mPendingThumbnails.size() > 0) { + + if (!mService.mPendingThumbnails.isEmpty()) { // There are clients waiting to receive thumbnails so, in case // this is an activity that someone is waiting for, add it // to the pending list so we can correctly update the clients. - mService.mCancelledThumbnails.add(r); + mStackSupervisor.mCancelledThumbnails.add(r); } - if (immediate) { - return finishCurrentActivityLocked(r, index, - FINISH_IMMEDIATELY, oomAdj) == null; - } else if (mResumedActivity == r) { - boolean endTask = index <= 0 - || (mHistory.get(index-1)).task != r.task; - if (DEBUG_TRANSITION) Slog.v(TAG, + if (mResumedActivity == r) { + boolean endTask = index <= 0; + if (DEBUG_VISBILITY || DEBUG_TRANSITION) Slog.v(TAG, "Prepare close transition: finishing " + r); - mService.mWindowManager.prepareAppTransition(endTask + mWindowManager.prepareAppTransition(endTask ? AppTransition.TRANSIT_TASK_CLOSE : AppTransition.TRANSIT_ACTIVITY_CLOSE, false); - + // Tell window manager to prepare for this one to be removed. - mService.mWindowManager.setAppVisibility(r.appToken, false); - + mWindowManager.setAppVisibility(r.appToken, false); + if (mPausingActivity == null) { if (DEBUG_PAUSE) Slog.v(TAG, "Finish needs to pause: " + r); if (DEBUG_USER_LEAVING) Slog.v(TAG, "finish() => pause with userLeaving=false"); @@ -3838,8 +2412,7 @@ final class ActivityStack { // If the activity is PAUSING, we will complete the finish once // it is done pausing; else we can just directly finish it here. if (DEBUG_PAUSE) Slog.v(TAG, "Finish not pausing: " + r); - return finishCurrentActivityLocked(r, index, - FINISH_AFTER_PAUSE, oomAdj) == null; + return finishCurrentActivityLocked(r, FINISH_AFTER_PAUSE, oomAdj) == null; } else { if (DEBUG_PAUSE) Slog.v(TAG, "Finish waiting for pause of: " + r); } @@ -3847,35 +2420,26 @@ final class ActivityStack { return false; } - private static final int FINISH_IMMEDIATELY = 0; - private static final int FINISH_AFTER_PAUSE = 1; - private static final int FINISH_AFTER_VISIBLE = 2; - - private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, - int mode, boolean oomAdj) { - final int index = indexOfActivityLocked(r); - if (index < 0) { - return null; - } - - return finishCurrentActivityLocked(r, index, mode, oomAdj); - } + static final int FINISH_IMMEDIATELY = 0; + static final int FINISH_AFTER_PAUSE = 1; + static final int FINISH_AFTER_VISIBLE = 2; - private final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, - int index, int mode, boolean oomAdj) { + final ActivityRecord finishCurrentActivityLocked(ActivityRecord r, int mode, boolean oomAdj) { // First things first: if this activity is currently visible, // and the resumed activity is not yet visible, then hold off on // finishing until the resumed one becomes visible. if (mode == FINISH_AFTER_VISIBLE && r.nowVisible) { - if (!mStoppingActivities.contains(r)) { - mStoppingActivities.add(r); - if (mStoppingActivities.size() > 3) { + if (!mStackSupervisor.mStoppingActivities.contains(r)) { + mStackSupervisor.mStoppingActivities.add(r); + if (mStackSupervisor.mStoppingActivities.size() > 3 + || r.frontOfTask && mTaskHistory.size() <= 1) { // If we already have a few activities waiting to stop, // then give up on things going idle and start clearing - // them out. - scheduleIdleLocked(); + // them out. Or if r is the last of activity of the last task the stack + // will be empty and must be cleared immediately. + mStackSupervisor.scheduleIdleLocked(); } else { - checkReadyForSleepLocked(); + mStackSupervisor.checkReadyForSleepLocked(); } } if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPING: " + r @@ -3888,9 +2452,9 @@ final class ActivityStack { } // make sure the record is cleaned out of other places. - mStoppingActivities.remove(r); - mGoingToSleepActivities.remove(r); - mWaitingVisibleActivities.remove(r); + mStackSupervisor.mStoppingActivities.remove(r); + mStackSupervisor.mGoingToSleepActivities.remove(r); + mStackSupervisor.mWaitingVisibleActivities.remove(r); if (mResumedActivity == r) { mResumedActivity = null; } @@ -3906,19 +2470,99 @@ final class ActivityStack { boolean activityRemoved = destroyActivityLocked(r, true, oomAdj, "finish-imm"); if (activityRemoved) { - resumeTopActivityLocked(null); + mStackSupervisor.resumeTopActivitiesLocked(); } return activityRemoved ? null : r; - } else { - // Need to go through the full pause cycle to get this - // activity into the stopped state and then finish it. - if (localLOGV) Slog.v(TAG, "Enqueueing pending finish: " + r); - mFinishingActivities.add(r); - resumeTopActivityLocked(null); } + + // Need to go through the full pause cycle to get this + // activity into the stopped state and then finish it. + if (localLOGV) Slog.v(TAG, "Enqueueing pending finish: " + r); + mStackSupervisor.mFinishingActivities.add(r); + mStackSupervisor.getFocusedStack().resumeTopActivityLocked(null); return r; } + final boolean navigateUpToLocked(IBinder token, Intent destIntent, int resultCode, + Intent resultData) { + final ActivityRecord srec = ActivityRecord.forToken(token); + final TaskRecord task = srec.task; + final ArrayList<ActivityRecord> activities = task.mActivities; + final int start = activities.indexOf(srec); + if (!mTaskHistory.contains(task) || (start < 0)) { + return false; + } + int finishTo = start - 1; + ActivityRecord parent = finishTo < 0 ? null : activities.get(finishTo); + boolean foundParentInTask = false; + final ComponentName dest = destIntent.getComponent(); + if (start > 0 && dest != null) { + for (int i = finishTo; i >= 0; i--) { + ActivityRecord r = activities.get(i); + if (r.info.packageName.equals(dest.getPackageName()) && + r.info.name.equals(dest.getClassName())) { + finishTo = i; + parent = r; + foundParentInTask = true; + break; + } + } + } + + IActivityController controller = mService.mController; + if (controller != null) { + ActivityRecord next = topRunningActivityLocked(srec.appToken, 0); + if (next != null) { + // ask watcher if this is allowed + boolean resumeOK = true; + try { + resumeOK = controller.activityResuming(next.packageName); + } catch (RemoteException e) { + mService.mController = null; + Watchdog.getInstance().setActivityController(null); + } + + if (!resumeOK) { + return false; + } + } + } + final long origId = Binder.clearCallingIdentity(); + for (int i = start; i > finishTo; i--) { + ActivityRecord r = activities.get(i); + requestFinishActivityLocked(r.appToken, resultCode, resultData, "navigate-up", true); + // Only return the supplied result for the first activity finished + resultCode = Activity.RESULT_CANCELED; + resultData = null; + } + + if (parent != null && foundParentInTask) { + final int parentLaunchMode = parent.info.launchMode; + final int destIntentFlags = destIntent.getFlags(); + if (parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE || + parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TASK || + parentLaunchMode == ActivityInfo.LAUNCH_SINGLE_TOP || + (destIntentFlags & Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { + parent.deliverNewIntentLocked(srec.info.applicationInfo.uid, destIntent); + } else { + try { + ActivityInfo aInfo = AppGlobals.getPackageManager().getActivityInfo( + destIntent.getComponent(), 0, srec.userId); + int res = mStackSupervisor.startActivityLocked(srec.app.thread, destIntent, + null, aInfo, parent.appToken, null, + 0, -1, parent.launchedFromUid, parent.launchedFromPackage, + 0, null, true, null); + foundParentInTask = res == ActivityManager.START_SUCCESS; + } catch (RemoteException e) { + foundParentInTask = false; + } + requestFinishActivityLocked(parent.appToken, resultCode, + resultData, "navigate-up", true); + } + } + Binder.restoreCallingIdentity(origId); + return foundParentInTask; + } /** * Perform the common clean-up of an activity record. This is called both * as part of destroyActivityLocked() (when destroying the client-side @@ -3948,9 +2592,9 @@ final class ActivityStack { // Make sure this record is no longer in the pending finishes list. // This could happen, for example, if we are trimming activities // down to the max limit while they are still waiting to finish. - mFinishingActivities.remove(r); - mWaitingVisibleActivities.remove(r); - + mStackSupervisor.mFinishingActivities.remove(r); + mStackSupervisor.mWaitingVisibleActivities.remove(r); + // Remove any pending results. if (r.finishing && r.pendingResults != null) { for (WeakReference<PendingIntentRecord> apr : r.pendingResults) { @@ -3963,14 +2607,14 @@ final class ActivityStack { } if (cleanServices) { - cleanUpActivityServicesLocked(r); + cleanUpActivityServicesLocked(r); } - if (mService.mPendingThumbnails.size() > 0) { + if (!mService.mPendingThumbnails.isEmpty()) { // There are clients waiting to receive thumbnails so, in case // this is an activity that someone is waiting for, add it // to the pending list so we can correctly update the clients. - mService.mCancelledThumbnails.add(r); + mStackSupervisor.mCancelledThumbnails.add(r); } // Get rid of any pending idle timeouts. @@ -3978,9 +2622,9 @@ final class ActivityStack { } private void removeTimeoutsForActivityLocked(ActivityRecord r) { + mStackSupervisor.removeTimeoutsForActivityLocked(r); mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); mHandler.removeMessages(STOP_TIMEOUT_MSG, r); - mHandler.removeMessages(IDLE_TIMEOUT_MSG, r); mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r); r.finishLaunchTickingLocked(); } @@ -3993,22 +2637,29 @@ final class ActivityStack { here.fillInStackTrace(); Slog.i(TAG, "Removing activity " + r + " from stack"); } - mHistory.remove(r); + final TaskRecord task = r.task; + if (task != null && task.removeActivity(r)) { + if (DEBUG_STACK) Slog.i(TAG, + "removeActivityFromHistoryLocked: last activity removed from " + this); + if (mStackSupervisor.isFrontStack(this) && task == topTask() && task.mOnTopOfHome) { + mStackSupervisor.moveHomeToTop(); + } + mStackSupervisor.removeTask(task); + } r.takeFromHistory(); removeTimeoutsForActivityLocked(r); - if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r - + " (removed from history)"); + if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r + " (removed from history)"); r.state = ActivityState.DESTROYED; if (DEBUG_APP) Slog.v(TAG, "Clearing app during remove for activity " + r); r.app = null; - mService.mWindowManager.removeAppToken(r.appToken); + mWindowManager.removeAppToken(r.appToken); if (VALIDATE_TOKENS) { validateAppTokensLocked(); } cleanUpActivityServicesLocked(r); r.removeUriPermissionsLocked(); } - + /** * Perform clean-up of service connections in an activity record. */ @@ -4033,36 +2684,40 @@ final class ActivityStack { final void destroyActivitiesLocked(ProcessRecord owner, boolean oomAdj, String reason) { boolean lastIsOpaque = false; boolean activityRemoved = false; - for (int i=mHistory.size()-1; i>=0; i--) { - ActivityRecord r = mHistory.get(i); - if (r.finishing) { - continue; - } - if (r.fullscreen) { - lastIsOpaque = true; - } - if (owner != null && r.app != owner) { - continue; - } - if (!lastIsOpaque) { - continue; - } - // We can destroy this one if we have its icicle saved and - // it is not in the process of pausing/stopping/finishing. - if (r.app != null && r != mResumedActivity && r != mPausingActivity - && r.haveState && !r.visible && r.stopped - && r.state != ActivityState.DESTROYING - && r.state != ActivityState.DESTROYED) { - if (DEBUG_SWITCH) Slog.v(TAG, "Destroying " + r + " in state " + r.state - + " resumed=" + mResumedActivity - + " pausing=" + mPausingActivity); - if (destroyActivityLocked(r, true, oomAdj, reason)) { - activityRemoved = true; + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if (r.finishing) { + continue; + } + if (r.fullscreen) { + lastIsOpaque = true; + } + if (owner != null && r.app != owner) { + continue; + } + if (!lastIsOpaque) { + continue; + } + // We can destroy this one if we have its icicle saved and + // it is not in the process of pausing/stopping/finishing. + if (r.app != null && r != mResumedActivity && r != mPausingActivity + && r.haveState && !r.visible && r.stopped + && r.state != ActivityState.DESTROYING + && r.state != ActivityState.DESTROYED) { + if (DEBUG_SWITCH) Slog.v(TAG, "Destroying " + r + " in state " + r.state + + " resumed=" + mResumedActivity + + " pausing=" + mPausingActivity); + if (destroyActivityLocked(r, true, oomAdj, reason)) { + activityRemoved = true; + } } } } if (activityRemoved) { - resumeTopActivityLocked(null); + mStackSupervisor.resumeTopActivitiesLocked(); + } } @@ -4089,23 +2744,21 @@ final class ActivityStack { if (hadApp) { if (removeFromApp) { - int idx = r.app.activities.indexOf(r); - if (idx >= 0) { - r.app.activities.remove(idx); - } + r.app.activities.remove(r); if (mService.mHeavyWeightProcess == r.app && r.app.activities.size() <= 0) { mService.mHeavyWeightProcess = null; mService.mHandler.sendEmptyMessage( ActivityManagerService.CANCEL_HEAVY_NOTIFICATION_MSG); } - if (r.app.activities.size() == 0) { - // No longer have activities, so update oom adj. + if (r.app.activities.isEmpty()) { + // No longer have activities, so update LRU list and oom adj. + mService.updateLruProcessLocked(r.app, false, false); mService.updateOomAdjLocked(); } } boolean skipDestroy = false; - + try { if (DEBUG_SWITCH) Slog.i(TAG, "Destroying: " + r); r.app.thread.scheduleDestroyActivity(r.appToken, r.finishing, @@ -4123,7 +2776,7 @@ final class ActivityStack { } r.nowVisible = false; - + // If the activity is finishing, we need to wait on removing it // from the list to give it a chance to do its cleanup. During // that time it may make calls back with its token so we need to @@ -4135,12 +2788,10 @@ final class ActivityStack { if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYING: " + r + " (destroy requested)"); r.state = ActivityState.DESTROYING; - Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG); - msg.obj = r; + Message msg = mHandler.obtainMessage(DESTROY_TIMEOUT_MSG, r); mHandler.sendMessageDelayed(msg, DESTROY_TIMEOUT); } else { - if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r - + " (destroy skipped)"); + if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r + " (destroy skipped)"); r.state = ActivityState.DESTROYED; if (DEBUG_APP) Slog.v(TAG, "Clearing app during destroy for activity " + r); r.app = null; @@ -4151,8 +2802,7 @@ final class ActivityStack { removeActivityFromHistoryLocked(r); removedFromHistory = true; } else { - if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r - + " (no app)"); + if (DEBUG_STATES) Slog.v(TAG, "Moving to DESTROYED: " + r + " (no app)"); r.state = ActivityState.DESTROYED; if (DEBUG_APP) Slog.v(TAG, "Clearing app during destroy for activity " + r); r.app = null; @@ -4160,46 +2810,43 @@ final class ActivityStack { } r.configChangeFlags = 0; - + if (!mLRUActivities.remove(r) && hadApp) { Slog.w(TAG, "Activity " + r + " being finished, but not in LRU list"); } - + return removedFromHistory; } - final void activityDestroyed(IBinder token) { - synchronized (mService) { - final long origId = Binder.clearCallingIdentity(); - try { - ActivityRecord r = ActivityRecord.forToken(token); - if (r != null) { - mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r); - } + final void activityDestroyedLocked(IBinder token) { + final long origId = Binder.clearCallingIdentity(); + try { + ActivityRecord r = ActivityRecord.forToken(token); + if (r != null) { + mHandler.removeMessages(DESTROY_TIMEOUT_MSG, r); + } - int index = indexOfActivityLocked(r); - if (index >= 0) { - if (r.state == ActivityState.DESTROYING) { - cleanUpActivityLocked(r, true, false); - removeActivityFromHistoryLocked(r); - } + if (isInStackLocked(token) != null) { + if (r.state == ActivityState.DESTROYING) { + cleanUpActivityLocked(r, true, false); + removeActivityFromHistoryLocked(r); } - resumeTopActivityLocked(null); - } finally { - Binder.restoreCallingIdentity(origId); } + mStackSupervisor.resumeTopActivitiesLocked(); + } finally { + Binder.restoreCallingIdentity(origId); } } - - private void removeHistoryRecordsForAppLocked(ArrayList list, ProcessRecord app, - String listName) { + + private void removeHistoryRecordsForAppLocked(ArrayList<ActivityRecord> list, + ProcessRecord app, String listName) { int i = list.size(); if (DEBUG_CLEANUP) Slog.v( TAG, "Removing app " + app + " from list " + listName + " with " + i + " entries"); while (i > 0) { i--; - ActivityRecord r = (ActivityRecord)list.get(i); + ActivityRecord r = list.get(i); if (DEBUG_CLEANUP) Slog.v(TAG, "Record #" + i + " " + r); if (r.app == app) { if (DEBUG_CLEANUP) Slog.v(TAG, "---> REMOVING this entry!"); @@ -4211,100 +2858,91 @@ final class ActivityStack { boolean removeHistoryRecordsForAppLocked(ProcessRecord app) { removeHistoryRecordsForAppLocked(mLRUActivities, app, "mLRUActivities"); - removeHistoryRecordsForAppLocked(mStoppingActivities, app, "mStoppingActivities"); - removeHistoryRecordsForAppLocked(mGoingToSleepActivities, app, "mGoingToSleepActivities"); - removeHistoryRecordsForAppLocked(mWaitingVisibleActivities, app, + removeHistoryRecordsForAppLocked(mStackSupervisor.mStoppingActivities, app, + "mStoppingActivities"); + removeHistoryRecordsForAppLocked(mStackSupervisor.mGoingToSleepActivities, app, + "mGoingToSleepActivities"); + removeHistoryRecordsForAppLocked(mStackSupervisor.mWaitingVisibleActivities, app, "mWaitingVisibleActivities"); - removeHistoryRecordsForAppLocked(mFinishingActivities, app, "mFinishingActivities"); + removeHistoryRecordsForAppLocked(mStackSupervisor.mFinishingActivities, app, + "mFinishingActivities"); boolean hasVisibleActivities = false; // Clean out the history list. - int i = mHistory.size(); + int i = numActivities(); if (DEBUG_CLEANUP) Slog.v( TAG, "Removing app " + app + " from history with " + i + " entries"); - while (i > 0) { - i--; - ActivityRecord r = (ActivityRecord)mHistory.get(i); - if (DEBUG_CLEANUP) Slog.v( - TAG, "Record #" + i + " " + r + ": app=" + r.app); - if (r.app == app) { - boolean remove; - if ((!r.haveState && !r.stateNotNeeded) || r.finishing) { - // Don't currently have state for the activity, or - // it is finishing -- always remove it. - remove = true; - } else if (r.launchCount > 2 && - r.lastLaunchTime > (SystemClock.uptimeMillis()-60000)) { - // We have launched this activity too many times since it was - // able to run, so give up and remove it. - remove = true; - } else { - // The process may be gone, but the activity lives on! - remove = false; - } - if (remove) { - if (ActivityStack.DEBUG_ADD_REMOVE || DEBUG_CLEANUP) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.i(TAG, "Removing activity " + r + " from stack at " + i - + ": haveState=" + r.haveState - + " stateNotNeeded=" + r.stateNotNeeded - + " finishing=" + r.finishing - + " state=" + r.state, here); - } - if (!r.finishing) { - Slog.w(TAG, "Force removing " + r + ": app died, no saved state"); - EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY, - r.userId, System.identityHashCode(r), - r.task.taskId, r.shortComponentName, - "proc died without state saved"); + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + --i; + if (DEBUG_CLEANUP) Slog.v( + TAG, "Record #" + i + " " + r + ": app=" + r.app); + if (r.app == app) { + boolean remove; + if ((!r.haveState && !r.stateNotNeeded) || r.finishing) { + // Don't currently have state for the activity, or + // it is finishing -- always remove it. + remove = true; + } else if (r.launchCount > 2 && + r.lastLaunchTime > (SystemClock.uptimeMillis()-60000)) { + // We have launched this activity too many times since it was + // able to run, so give up and remove it. + remove = true; + } else { + // The process may be gone, but the activity lives on! + remove = false; } - removeActivityFromHistoryLocked(r); + if (remove) { + if (DEBUG_ADD_REMOVE || DEBUG_CLEANUP) { + RuntimeException here = new RuntimeException("here"); + here.fillInStackTrace(); + Slog.i(TAG, "Removing activity " + r + " from stack at " + i + + ": haveState=" + r.haveState + + " stateNotNeeded=" + r.stateNotNeeded + + " finishing=" + r.finishing + + " state=" + r.state, here); + } + if (!r.finishing) { + Slog.w(TAG, "Force removing " + r + ": app died, no saved state"); + EventLog.writeEvent(EventLogTags.AM_FINISH_ACTIVITY, + r.userId, System.identityHashCode(r), + r.task.taskId, r.shortComponentName, + "proc died without state saved"); + if (r.state == ActivityState.RESUMED) { + mService.updateUsageStats(r, false); + } + } + removeActivityFromHistoryLocked(r); - } else { - // We have the current state for this activity, so - // it can be restarted later when needed. - if (localLOGV) Slog.v( - TAG, "Keeping entry, setting app to null"); - if (r.visible) { - hasVisibleActivities = true; - } - if (DEBUG_APP) Slog.v(TAG, "Clearing app during removeHistory for activity " - + r); - r.app = null; - r.nowVisible = false; - if (!r.haveState) { - if (ActivityStack.DEBUG_SAVED_STATE) Slog.i(TAG, - "App died, clearing saved state of " + r); - r.icicle = null; + } else { + // We have the current state for this activity, so + // it can be restarted later when needed. + if (localLOGV) Slog.v( + TAG, "Keeping entry, setting app to null"); + if (r.visible) { + hasVisibleActivities = true; + } + if (DEBUG_APP) Slog.v(TAG, "Clearing app during removeHistory for activity " + + r); + r.app = null; + r.nowVisible = false; + if (!r.haveState) { + if (DEBUG_SAVED_STATE) Slog.i(TAG, + "App died, clearing saved state of " + r); + r.icicle = null; + } } - } - r.stack.cleanUpActivityLocked(r, true, true); + cleanUpActivityLocked(r, true, true); + } } } return hasVisibleActivities; } - - /** - * Move the current home activity's task (if one exists) to the front - * of the stack. - */ - final void moveHomeToFrontLocked() { - TaskRecord homeTask = null; - for (int i=mHistory.size()-1; i>=0; i--) { - ActivityRecord hr = mHistory.get(i); - if (hr.isHomeActivity) { - homeTask = hr.task; - break; - } - } - if (homeTask != null) { - moveTaskToFrontLocked(homeTask, null, null); - } - } final void updateTransitLocked(int transit, Bundle options) { if (options != null) { @@ -4315,16 +2953,46 @@ final class ActivityStack { ActivityOptions.abort(options); } } - mService.mWindowManager.prepareAppTransition(transit, false); + mWindowManager.prepareAppTransition(transit, false); + } + + void moveHomeTaskToTop() { + final int top = mTaskHistory.size() - 1; + for (int taskNdx = top; taskNdx >= 0; --taskNdx) { + final TaskRecord task = mTaskHistory.get(taskNdx); + if (task.isHomeTask()) { + if (DEBUG_TASKS || DEBUG_STACK) Slog.d(TAG, "moveHomeTaskToTop: moving " + task); + mTaskHistory.remove(taskNdx); + mTaskHistory.add(top, task); + mWindowManager.moveTaskToTop(task.taskId); + return; + } + } + } + + final boolean findTaskToMoveToFrontLocked(int taskId, int flags, Bundle options) { + final TaskRecord task = taskForIdLocked(taskId); + if (task != null) { + if ((flags & ActivityManager.MOVE_TASK_NO_USER_ACTION) == 0) { + mStackSupervisor.mUserLeaving = true; + } + if ((flags & ActivityManager.MOVE_TASK_WITH_HOME) != 0) { + // Caller wants the home activity moved with it. To accomplish this, + // we'll just indicate that this task returns to the home task. + task.mOnTopOfHome = true; + } + moveTaskToFrontLocked(task, null, options); + return true; + } + return false; } final void moveTaskToFrontLocked(TaskRecord tr, ActivityRecord reason, Bundle options) { if (DEBUG_SWITCH) Slog.v(TAG, "moveTaskToFront: " + tr); - final int task = tr.taskId; - int top = mHistory.size()-1; - - if (top < 0 || (mHistory.get(top)).task.taskId == task) { + final int numTasks = mTaskHistory.size(); + final int index = mTaskHistory.indexOf(tr); + if (numTasks == 0 || index < 0) { // nothing to do! if (reason != null && (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { @@ -4335,40 +3003,16 @@ final class ActivityStack { return; } - ArrayList<IBinder> moved = new ArrayList<IBinder>(); - - // Applying the affinities may have removed entries from the history, - // so get the size again. - top = mHistory.size()-1; - int pos = top; + mStackSupervisor.moveHomeStack(isHomeStack()); // Shift all activities with this task up to the top // of the stack, keeping them in the same internal order. - while (pos >= 0) { - ActivityRecord r = mHistory.get(pos); - if (localLOGV) Slog.v( - TAG, "At " + pos + " ckp " + r.task + ": " + r); - if (r.task.taskId == task) { - if (localLOGV) Slog.v(TAG, "Removing and adding at " + top); - if (DEBUG_ADD_REMOVE) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.i(TAG, "Removing and adding activity " + r + " to stack at " + top, here); - } - mHistory.remove(pos); - mHistory.add(top, r); - moved.add(0, r.appToken); - top--; - } - pos--; - } + insertTaskAtTop(tr); - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare to front transition: task=" + tr); + if (DEBUG_TRANSITION) Slog.v(TAG, "Prepare to front transition: task=" + tr); if (reason != null && (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mService.mWindowManager.prepareAppTransition( - AppTransition.TRANSIT_NONE, false); + mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false); ActivityRecord r = topRunningActivityLocked(null); if (r != null) { mNoAnimActivities.add(r); @@ -4377,39 +3021,36 @@ final class ActivityStack { } else { updateTransitLocked(AppTransition.TRANSIT_TASK_TO_FRONT, options); } - - mService.mWindowManager.moveAppTokensToTop(moved); + + mWindowManager.moveTaskToTop(tr.taskId); + + mStackSupervisor.resumeTopActivitiesLocked(); + EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, tr.userId, tr.taskId); + if (VALIDATE_TOKENS) { validateAppTokensLocked(); } - - finishTaskMoveLocked(task); - EventLog.writeEvent(EventLogTags.AM_TASK_TO_FRONT, tr.userId, task); - } - - private final void finishTaskMoveLocked(int task) { - resumeTopActivityLocked(null); } /** - * Worker method for rearranging history stack. Implements the function of moving all - * activities for a specific task (gathering them if disjoint) into a single group at the + * Worker method for rearranging history stack. Implements the function of moving all + * activities for a specific task (gathering them if disjoint) into a single group at the * bottom of the stack. - * + * * If a watcher is installed, the action is preflighted and the watcher has an opportunity * to premeptively cancel the move. - * - * @param task The taskId to collect and move to the bottom. + * + * @param taskId The taskId to collect and move to the bottom. * @return Returns true if the move completed, false if not. */ - final boolean moveTaskToBackLocked(int task, ActivityRecord reason) { - Slog.i(TAG, "moveTaskToBack: " + task); - + final boolean moveTaskToBackLocked(int taskId, ActivityRecord reason) { + Slog.i(TAG, "moveTaskToBack: " + taskId); + // If we have a watcher, preflight the move before committing to it. First check // for *other* available tasks, but if none are available, then try again allowing the // current task to be selected. - if (mMainStack && mService.mController != null) { - ActivityRecord next = topRunningActivityLocked(null, task); + if (mStackSupervisor.isFrontStack(this) && mService.mController != null) { + ActivityRecord next = topRunningActivityLocked(null, taskId); if (next == null) { next = topRunningActivityLocked(null, 0); } @@ -4420,6 +3061,7 @@ final class ActivityStack { moveOK = mService.mController.activityResuming(next.packageName); } catch (RemoteException e) { mService.mController = null; + Watchdog.getInstance().setActivityController(null); } if (!moveOK) { return false; @@ -4427,181 +3069,59 @@ final class ActivityStack { } } - ArrayList<IBinder> moved = new ArrayList<IBinder>(); - if (DEBUG_TRANSITION) Slog.v(TAG, - "Prepare to back transition: task=" + task); - - final int N = mHistory.size(); - int bottom = 0; - int pos = 0; + "Prepare to back transition: task=" + taskId); - // Shift all activities with this task down to the bottom - // of the stack, keeping them in the same internal order. - while (pos < N) { - ActivityRecord r = mHistory.get(pos); - if (localLOGV) Slog.v( - TAG, "At " + pos + " ckp " + r.task + ": " + r); - if (r.task.taskId == task) { - if (localLOGV) Slog.v(TAG, "Removing and adding at " + (N-1)); - if (DEBUG_ADD_REMOVE) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.i(TAG, "Removing and adding activity " + r + " to stack at " - + bottom, here); - } - mHistory.remove(pos); - mHistory.add(bottom, r); - moved.add(r.appToken); - bottom++; + final TaskRecord tr = taskForIdLocked(taskId); + if (tr == null) { + return false; + } + + mTaskHistory.remove(tr); + mTaskHistory.add(0, tr); + + // There is an assumption that moving a task to the back moves it behind the home activity. + // We make sure here that some activity in the stack will launch home. + ActivityRecord lastActivity = null; + int numTasks = mTaskHistory.size(); + for (int taskNdx = numTasks - 1; taskNdx >= 1; --taskNdx) { + final TaskRecord task = mTaskHistory.get(taskNdx); + if (task.mOnTopOfHome) { + break; + } + if (taskNdx == 1) { + // Set the last task before tr to go to home. + task.mOnTopOfHome = true; } - pos++; } if (reason != null && - (reason.intent.getFlags()&Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { - mService.mWindowManager.prepareAppTransition( - AppTransition.TRANSIT_NONE, false); + (reason.intent.getFlags() & Intent.FLAG_ACTIVITY_NO_ANIMATION) != 0) { + mWindowManager.prepareAppTransition(AppTransition.TRANSIT_NONE, false); ActivityRecord r = topRunningActivityLocked(null); if (r != null) { mNoAnimActivities.add(r); } } else { - mService.mWindowManager.prepareAppTransition( - AppTransition.TRANSIT_TASK_TO_BACK, false); + mWindowManager.prepareAppTransition(AppTransition.TRANSIT_TASK_TO_BACK, false); } - mService.mWindowManager.moveAppTokensToBottom(moved); + mWindowManager.moveTaskToBottom(taskId); + if (VALIDATE_TOKENS) { validateAppTokensLocked(); } - finishTaskMoveLocked(task); - return true; - } - - public ActivityManager.TaskThumbnails getTaskThumbnailsLocked(TaskRecord tr) { - TaskAccessInfo info = getTaskAccessInfoLocked(tr.taskId, true); - ActivityRecord resumed = mResumedActivity; - if (resumed != null && resumed.thumbHolder == tr) { - info.mainThumbnail = resumed.stack.screenshotActivities(resumed); + final TaskRecord task = mResumedActivity != null ? mResumedActivity.task : null; + if (task == tr && task.mOnTopOfHome || numTasks <= 1) { + task.mOnTopOfHome = false; + return mStackSupervisor.resumeHomeActivity(null); } - if (info.mainThumbnail == null) { - info.mainThumbnail = tr.lastThumbnail; - } - return info; - } - public Bitmap getTaskTopThumbnailLocked(TaskRecord tr) { - ActivityRecord resumed = mResumedActivity; - if (resumed != null && resumed.task == tr) { - // This task is the current resumed task, we just need to take - // a screenshot of it and return that. - return resumed.stack.screenshotActivities(resumed); - } - // Return the information about the task, to figure out the top - // thumbnail to return. - TaskAccessInfo info = getTaskAccessInfoLocked(tr.taskId, true); - if (info.numSubThumbbails <= 0) { - return info.mainThumbnail != null ? info.mainThumbnail : tr.lastThumbnail; - } else { - return info.subtasks.get(info.numSubThumbbails-1).holder.lastThumbnail; - } - } - - public ActivityRecord removeTaskActivitiesLocked(int taskId, int subTaskIndex, - boolean taskRequired) { - TaskAccessInfo info = getTaskAccessInfoLocked(taskId, false); - if (info.root == null) { - if (taskRequired) { - Slog.w(TAG, "removeTaskLocked: unknown taskId " + taskId); - } - return null; - } - - if (subTaskIndex < 0) { - // Just remove the entire task. - performClearTaskAtIndexLocked(taskId, info.rootIndex); - return info.root; - } - - if (subTaskIndex >= info.subtasks.size()) { - if (taskRequired) { - Slog.w(TAG, "removeTaskLocked: unknown subTaskIndex " + subTaskIndex); - } - return null; - } - - // Remove all of this task's activities starting at the sub task. - TaskAccessInfo.SubTask subtask = info.subtasks.get(subTaskIndex); - performClearTaskAtIndexLocked(taskId, subtask.index); - return subtask.activity; - } - - public TaskAccessInfo getTaskAccessInfoLocked(int taskId, boolean inclThumbs) { - final TaskAccessInfo thumbs = new TaskAccessInfo(); - // How many different sub-thumbnails? - final int NA = mHistory.size(); - int j = 0; - ThumbnailHolder holder = null; - while (j < NA) { - ActivityRecord ar = mHistory.get(j); - if (!ar.finishing && ar.task.taskId == taskId) { - thumbs.root = ar; - thumbs.rootIndex = j; - holder = ar.thumbHolder; - if (holder != null) { - thumbs.mainThumbnail = holder.lastThumbnail; - } - j++; - break; - } - j++; - } - - if (j >= NA) { - return thumbs; - } - - ArrayList<TaskAccessInfo.SubTask> subtasks = new ArrayList<TaskAccessInfo.SubTask>(); - thumbs.subtasks = subtasks; - while (j < NA) { - ActivityRecord ar = mHistory.get(j); - j++; - if (ar.finishing) { - continue; - } - if (ar.task.taskId != taskId) { - break; - } - if (ar.thumbHolder != holder && holder != null) { - thumbs.numSubThumbbails++; - holder = ar.thumbHolder; - TaskAccessInfo.SubTask sub = new TaskAccessInfo.SubTask(); - sub.holder = holder; - sub.activity = ar; - sub.index = j-1; - subtasks.add(sub); - } - } - if (thumbs.numSubThumbbails > 0) { - thumbs.retriever = new IThumbnailRetriever.Stub() { - public Bitmap getThumbnail(int index) { - if (index < 0 || index >= thumbs.subtasks.size()) { - return null; - } - TaskAccessInfo.SubTask sub = thumbs.subtasks.get(index); - ActivityRecord resumed = mResumedActivity; - if (resumed != null && resumed.thumbHolder == sub.holder) { - return resumed.stack.screenshotActivities(resumed); - } - return sub.holder.lastThumbnail; - } - }; - } - return thumbs; + mStackSupervisor.resumeTopActivitiesLocked(); + return true; } - private final void logStartActivity(int tag, ActivityRecord r, + static final void logStartActivity(int tag, ActivityRecord r, TaskRecord task) { final Uri data = r.intent.getData(); final String strData = data != null ? data.toSafeString() : null; @@ -4626,10 +3146,10 @@ final class ActivityStack { "Skipping config check (will change): " + r); return true; } - + if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.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 = mService.mConfiguration; @@ -4638,7 +3158,7 @@ final class ActivityStack { "Configuration unchanged in " + r); return true; } - + // We don't worry about activities that are finishing. if (r.finishing) { if (DEBUG_SWITCH || DEBUG_CONFIGURATION) Slog.v(TAG, @@ -4646,7 +3166,7 @@ final class ActivityStack { r.stopFreezingScreenLocked(false); return true; } - + // Okay we now are going to make this activity have the new config. // But then we need to figure out how it needs to deal with that. Configuration oldConfig = r.configuration; @@ -4672,7 +3192,7 @@ final class ActivityStack { r.forceNewConfig = false; return true; } - + // Figure out how to handle the changes between the configurations. if (DEBUG_SWITCH || DEBUG_CONFIGURATION) { Slog.v(TAG, "Checking to restart " + r.info.name + ": changed=0x" @@ -4712,12 +3232,12 @@ final class ActivityStack { relaunchActivityLocked(r, r.configChangeFlags, false); r.configChangeFlags = 0; } - + // All done... tell the caller we weren't able to keep this // activity around. return false; } - + // Default case: the activity can handle this new configuration, so // hand it over. Note that we don't need to give it the new // configuration, since we always send configuration changes to all @@ -4732,11 +3252,11 @@ final class ActivityStack { } } r.stopFreezingScreenLocked(false); - + return true; } - private final boolean relaunchActivityLocked(ActivityRecord r, + private boolean relaunchActivityLocked(ActivityRecord r, int changes, boolean andResume) { List<ResultInfo> results = null; List<Intent> newIntents = null; @@ -4750,9 +3270,9 @@ final class ActivityStack { EventLog.writeEvent(andResume ? EventLogTags.AM_RELAUNCH_RESUME_ACTIVITY : EventLogTags.AM_RELAUNCH_ACTIVITY, r.userId, System.identityHashCode(r), r.task.taskId, r.shortComponentName); - + r.startFreezingScreenLocked(r.app, 0); - + try { if (DEBUG_SWITCH || DEBUG_STATES) Slog.i(TAG, (andResume ? "Relaunching to RESUMED " : "Relaunching to PAUSED ") @@ -4770,9 +3290,6 @@ final class ActivityStack { if (andResume) { r.results = null; r.newIntents = null; - if (mMainStack) { - mService.reportResumedActivityLocked(r); - } r.state = ActivityState.RESUMED; } else { mHandler.removeMessages(PAUSE_TIMEOUT_MSG, r); @@ -4781,8 +3298,292 @@ final class ActivityStack { return true; } - - public void dismissKeyguardOnNextActivityLocked() { - mDismissKeyguardOnNextActivity = true; + + boolean willActivityBeVisibleLocked(IBinder token) { + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if (r.appToken == token) { + return true; + } + if (r.fullscreen && !r.finishing) { + return false; + } + } + } + return true; + } + + void closeSystemDialogsLocked() { + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if ((r.info.flags&ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS) != 0) { + finishActivityLocked(r, Activity.RESULT_CANCELED, null, "close-sys", true); + } + } + } + } + + boolean forceStopPackageLocked(String name, boolean doit, boolean evenPersistent, int userId) { + boolean didSomething = false; + TaskRecord lastTask = null; + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; + int numActivities = activities.size(); + for (int activityNdx = 0; activityNdx < numActivities; ++activityNdx) { + ActivityRecord r = activities.get(activityNdx); + final boolean samePackage = r.packageName.equals(name) + || (name == null && r.userId == userId); + if ((userId == UserHandle.USER_ALL || r.userId == userId) + && (samePackage || r.task == lastTask) + && (r.app == null || evenPersistent || !r.app.persistent)) { + if (!doit) { + if (r.finishing) { + // If this activity is just finishing, then it is not + // interesting as far as something to stop. + continue; + } + return true; + } + didSomething = true; + Slog.i(TAG, " Force finishing activity " + r); + if (samePackage) { + if (r.app != null) { + r.app.removed = true; + } + r.app = null; + } + lastTask = r.task; + if (finishActivityLocked(r, Activity.RESULT_CANCELED, null, "force-stop", + true)) { + // r has been deleted from mActivities, accommodate. + --numActivities; + --activityNdx; + } + } + } + } + return didSomething; + } + + ActivityRecord getTasksLocked(IThumbnailReceiver receiver, + PendingThumbnailsRecord pending, List<RunningTaskInfo> list) { + ActivityRecord topRecord = null; + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final TaskRecord task = mTaskHistory.get(taskNdx); + ActivityRecord r = null; + ActivityRecord top = null; + int numActivities = 0; + int numRunning = 0; + final ArrayList<ActivityRecord> activities = task.mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + r = activities.get(activityNdx); + + // Initialize state for next task if needed. + if (top == null || (top.state == ActivityState.INITIALIZING)) { + top = r; + numActivities = numRunning = 0; + } + + // Add 'r' into the current task. + numActivities++; + if (r.app != null && r.app.thread != null) { + numRunning++; + } + + if (localLOGV) Slog.v( + TAG, r.intent.getComponent().flattenToShortString() + + ": task=" + r.task); + } + + RunningTaskInfo ci = new RunningTaskInfo(); + ci.id = task.taskId; + ci.baseActivity = r.intent.getComponent(); + ci.topActivity = top.intent.getComponent(); + ci.lastActiveTime = task.lastActiveTime; + + if (top.thumbHolder != null) { + ci.description = top.thumbHolder.lastDescription; + } + ci.numActivities = numActivities; + ci.numRunning = numRunning; + //System.out.println( + // "#" + maxNum + ": " + " descr=" + ci.description); + if (receiver != null) { + if (localLOGV) Slog.v( + TAG, "State=" + top.state + "Idle=" + top.idle + + " app=" + top.app + + " thr=" + (top.app != null ? top.app.thread : null)); + if (top.state == ActivityState.RESUMED || top.state == ActivityState.PAUSING) { + if (top.idle && top.app != null && top.app.thread != null) { + topRecord = top; + } else { + top.thumbnailNeeded = true; + } + } + pending.pendingRecords.add(top); + } + list.add(ci); + } + return topRecord; + } + + public void unhandledBackLocked() { + final int top = mTaskHistory.size() - 1; + if (DEBUG_SWITCH) Slog.d( + TAG, "Performing unhandledBack(): top activity at " + top); + if (top >= 0) { + final ArrayList<ActivityRecord> activities = mTaskHistory.get(top).mActivities; + int activityTop = activities.size() - 1; + if (activityTop > 0) { + finishActivityLocked(activities.get(activityTop), Activity.RESULT_CANCELED, null, + "unhandled-back", true); + } + } + } + + /** + * Reset local parameters because an app's activity died. + * @param app The app of the activity that died. + * @return result from removeHistoryRecordsForAppLocked. + */ + boolean handleAppDiedLocked(ProcessRecord app) { + if (mPausingActivity != null && mPausingActivity.app == app) { + if (DEBUG_PAUSE || DEBUG_CLEANUP) Slog.v(TAG, + "App died while pausing: " + mPausingActivity); + mPausingActivity = null; + } + if (mLastPausedActivity != null && mLastPausedActivity.app == app) { + mLastPausedActivity = null; + mLastNoHistoryActivity = null; + } + + return removeHistoryRecordsForAppLocked(app); + } + + void handleAppCrashLocked(ProcessRecord app) { + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord r = activities.get(activityNdx); + if (r.app == app) { + Slog.w(TAG, " Force finishing activity " + + r.intent.getComponent().flattenToShortString()); + finishActivityLocked(r, Activity.RESULT_CANCELED, null, "crashed", false); + } + } + } + } + + boolean dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, boolean dumpAll, + boolean dumpClient, String dumpPackage, boolean needSep, String header) { + boolean printed = false; + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final TaskRecord task = mTaskHistory.get(taskNdx); + printed |= ActivityStackSupervisor.dumpHistoryList(fd, pw, + mTaskHistory.get(taskNdx).mActivities, " ", "Hist", true, !dumpAll, + dumpClient, dumpPackage, needSep, header, + " Task id #" + task.taskId); + if (printed) { + header = null; + } + } + return printed; + } + + ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) { + ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>(); + + if ("all".equals(name)) { + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + activities.addAll(mTaskHistory.get(taskNdx).mActivities); + } + } else if ("top".equals(name)) { + final int top = mTaskHistory.size() - 1; + if (top >= 0) { + final ArrayList<ActivityRecord> list = mTaskHistory.get(top).mActivities; + int listTop = list.size() - 1; + if (listTop >= 0) { + activities.add(list.get(listTop)); + } + } + } else { + ItemMatcher matcher = new ItemMatcher(); + matcher.build(name); + + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + for (ActivityRecord r1 : mTaskHistory.get(taskNdx).mActivities) { + if (matcher.match(r1, r1.intent.getComponent())) { + activities.add(r1); + } + } + } + } + + return activities; + } + + ActivityRecord restartPackage(String packageName) { + ActivityRecord starting = topRunningActivityLocked(null); + + // All activities that came from the package must be + // restarted as if there was a config change. + for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) { + final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ActivityRecord a = activities.get(activityNdx); + if (a.info.packageName.equals(packageName)) { + a.forceNewConfig = true; + if (starting != null && a == starting && a.visible) { + a.startFreezingScreenLocked(starting.app, + ActivityInfo.CONFIG_SCREEN_LAYOUT); + } + } + } + } + + return starting; + } + + boolean removeTask(TaskRecord task) { + final int taskNdx = mTaskHistory.indexOf(task); + final int topTaskNdx = mTaskHistory.size() - 1; + if (task.mOnTopOfHome && taskNdx < topTaskNdx) { + mTaskHistory.get(taskNdx + 1).mOnTopOfHome = true; + } + mTaskHistory.remove(task); + return mTaskHistory.isEmpty(); + } + + TaskRecord createTaskRecord(int taskId, ActivityInfo info, Intent intent, boolean toTop) { + TaskRecord task = new TaskRecord(taskId, info, intent); + addTask(task, toTop); + return task; + } + + ArrayList<TaskRecord> getAllTasks() { + return new ArrayList<TaskRecord>(mTaskHistory); + } + + void addTask(final TaskRecord task, final boolean toTop) { + task.stack = this; + if (toTop) { + insertTaskAtTop(task); + } else { + mTaskHistory.add(0, task); + } + } + + public int getStackId() { + return mStackId; + } + + @Override + public String toString() { + return "ActivityStack{" + Integer.toHexString(System.identityHashCode(this)) + + " stackId=" + mStackId + ", " + mTaskHistory.size() + " tasks}"; } } diff --git a/services/java/com/android/server/am/ActivityStackSupervisor.java b/services/java/com/android/server/am/ActivityStackSupervisor.java new file mode 100644 index 000000000000..523015da0f29 --- /dev/null +++ b/services/java/com/android/server/am/ActivityStackSupervisor.java @@ -0,0 +1,2632 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import static android.Manifest.permission.START_ANY_ACTIVITY; +import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK; +import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME; +import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static com.android.server.am.ActivityManagerService.localLOGV; +import static com.android.server.am.ActivityManagerService.DEBUG_CONFIGURATION; +import static com.android.server.am.ActivityManagerService.DEBUG_FOCUS; +import static com.android.server.am.ActivityManagerService.DEBUG_PAUSE; +import static com.android.server.am.ActivityManagerService.DEBUG_RESULTS; +import static com.android.server.am.ActivityManagerService.DEBUG_STACK; +import static com.android.server.am.ActivityManagerService.DEBUG_SWITCH; +import static com.android.server.am.ActivityManagerService.DEBUG_TASKS; +import static com.android.server.am.ActivityManagerService.DEBUG_USER_LEAVING; +import static com.android.server.am.ActivityManagerService.FIRST_SUPERVISOR_STACK_MSG; +import static com.android.server.am.ActivityManagerService.TAG; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.AppGlobals; +import android.app.IActivityManager; +import android.app.IApplicationThread; +import android.app.IThumbnailReceiver; +import android.app.PendingIntent; +import android.app.ActivityManager.RunningTaskInfo; +import android.app.IActivityManager.WaitResult; +import android.app.ResultInfo; +import android.content.ComponentName; +import android.content.Context; +import android.content.IIntentSender; +import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.ActivityInfo; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Configuration; +import android.os.Binder; +import android.os.Bundle; +import android.os.Debug; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.PowerManager; +import android.os.Process; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.util.EventLog; +import android.util.Slog; +import android.util.SparseIntArray; + +import com.android.internal.app.HeavyWeightSwitcherActivity; +import com.android.internal.os.TransferPipe; +import com.android.server.am.ActivityManagerService.PendingActivityLaunch; +import com.android.server.am.ActivityStack.ActivityState; +import com.android.server.wm.StackBox; +import com.android.server.wm.WindowManagerService; + +import java.io.FileDescriptor; +import java.io.IOException; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.List; + +public final class ActivityStackSupervisor { + static final boolean DEBUG = ActivityManagerService.DEBUG || false; + static final boolean DEBUG_ADD_REMOVE = DEBUG || false; + static final boolean DEBUG_APP = DEBUG || false; + static final boolean DEBUG_SAVED_STATE = DEBUG || false; + static final boolean DEBUG_STATES = DEBUG || false; + static final boolean DEBUG_IDLE = DEBUG || false; + + public static final int HOME_STACK_ID = 0; + + /** How long we wait until giving up on the last activity telling us it is idle. */ + static final int IDLE_TIMEOUT = 10*1000; + + /** How long we can hold the sleep wake lock before giving up. */ + static final int SLEEP_TIMEOUT = 5*1000; + + // How long we can hold the launch wake lock before giving up. + static final int LAUNCH_TIMEOUT = 10*1000; + + static final int IDLE_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG; + static final int IDLE_NOW_MSG = FIRST_SUPERVISOR_STACK_MSG + 1; + static final int RESUME_TOP_ACTIVITY_MSG = FIRST_SUPERVISOR_STACK_MSG + 2; + static final int SLEEP_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 3; + static final int LAUNCH_TIMEOUT_MSG = FIRST_SUPERVISOR_STACK_MSG + 4; + + // For debugging to make sure the caller when acquiring/releasing our + // wake lock is the system process. + static final boolean VALIDATE_WAKE_LOCK_CALLER = false; + + final ActivityManagerService mService; + final Context mContext; + final Looper mLooper; + + final ActivityStackSupervisorHandler mHandler; + + /** Short cut */ + WindowManagerService mWindowManager; + + /** Dismiss the keyguard after the next activity is displayed? */ + boolean mDismissKeyguardOnNextActivity = false; + + /** Identifier counter for all ActivityStacks */ + private int mLastStackId = HOME_STACK_ID; + + /** Task identifier that activities are currently being started in. Incremented each time a + * new task is created. */ + private int mCurTaskId = 0; + + /** The current user */ + private int mCurrentUser; + + /** The stack containing the launcher app */ + private ActivityStack mHomeStack; + + /** The non-home stack currently receiving input or launching the next activity. If home is + * in front then mHomeStack overrides mFocusedStack. + * DO NOT ACCESS DIRECTLY - It may be null, use getFocusedStack() */ + private ActivityStack mFocusedStack; + + /** All the non-launcher stacks */ + private ArrayList<ActivityStack> mStacks = new ArrayList<ActivityStack>(); + + private static final int STACK_STATE_HOME_IN_FRONT = 0; + private static final int STACK_STATE_HOME_TO_BACK = 1; + private static final int STACK_STATE_HOME_IN_BACK = 2; + private static final int STACK_STATE_HOME_TO_FRONT = 3; + private int mStackState = STACK_STATE_HOME_IN_FRONT; + + /** List of activities that are waiting for a new activity to become visible before completing + * whatever operation they are supposed to do. */ + final ArrayList<ActivityRecord> mWaitingVisibleActivities = new ArrayList<ActivityRecord>(); + + /** List of processes waiting to find out about the next visible activity. */ + final ArrayList<IActivityManager.WaitResult> mWaitingActivityVisible = + new ArrayList<IActivityManager.WaitResult>(); + + /** List of processes waiting to find out about the next launched activity. */ + final ArrayList<IActivityManager.WaitResult> mWaitingActivityLaunched = + new ArrayList<IActivityManager.WaitResult>(); + + /** List of activities that are ready to be stopped, but waiting for the next activity to + * settle down before doing so. */ + final ArrayList<ActivityRecord> mStoppingActivities = new ArrayList<ActivityRecord>(); + + /** List of activities that are ready to be finished, but waiting for the previous activity to + * settle down before doing so. It contains ActivityRecord objects. */ + final ArrayList<ActivityRecord> mFinishingActivities = new ArrayList<ActivityRecord>(); + + /** List of activities that are in the process of going to sleep. */ + final ArrayList<ActivityRecord> mGoingToSleepActivities = new ArrayList<ActivityRecord>(); + + /** List of ActivityRecord objects that have been finished and must still report back to a + * pending thumbnail receiver. */ + final ArrayList<ActivityRecord> mCancelledThumbnails = new ArrayList<ActivityRecord>(); + + /** Used on user changes */ + final ArrayList<UserStartedState> mStartingUsers = new ArrayList<UserStartedState>(); + + /** Set to indicate whether to issue an onUserLeaving callback when a newly launched activity + * is being brought in front of us. */ + boolean mUserLeaving = false; + + /** Set when we have taken too long waiting to go to sleep. */ + boolean mSleepTimeout = false; + + /** + * We don't want to allow the device to go to sleep while in the process + * of launching an activity. This is primarily to allow alarm intent + * receivers to launch an activity and get that to run before the device + * goes back to sleep. + */ + final PowerManager.WakeLock mLaunchingActivity; + + /** + * Set when the system is going to sleep, until we have + * successfully paused the current activity and released our wake lock. + * At that point the system is allowed to actually sleep. + */ + final PowerManager.WakeLock mGoingToSleep; + + /** Stack id of the front stack when user switched, indexed by userId. */ + SparseIntArray mUserStackInFront = new SparseIntArray(2); + + public ActivityStackSupervisor(ActivityManagerService service, Context context, + Looper looper) { + mService = service; + mContext = context; + mLooper = looper; + PowerManager pm = (PowerManager)context.getSystemService(Context.POWER_SERVICE); + mGoingToSleep = pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Sleep"); + mHandler = new ActivityStackSupervisorHandler(looper); + if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) { + throw new IllegalStateException("Calling must be system uid"); + } + mLaunchingActivity = + pm.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, "ActivityManager-Launch"); + mLaunchingActivity.setReferenceCounted(false); + } + + void setWindowManager(WindowManagerService wm) { + mWindowManager = wm; + mHomeStack = new ActivityStack(mService, mContext, mLooper, HOME_STACK_ID); + mStacks.add(mHomeStack); + } + + void dismissKeyguard() { + if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen(""); + if (mDismissKeyguardOnNextActivity) { + mDismissKeyguardOnNextActivity = false; + mWindowManager.dismissKeyguard(); + } + } + + ActivityStack getFocusedStack() { + if (mFocusedStack == null) { + return mHomeStack; + } + switch (mStackState) { + case STACK_STATE_HOME_IN_FRONT: + case STACK_STATE_HOME_TO_FRONT: + return mHomeStack; + case STACK_STATE_HOME_IN_BACK: + case STACK_STATE_HOME_TO_BACK: + default: + return mFocusedStack; + } + } + + ActivityStack getLastStack() { + switch (mStackState) { + case STACK_STATE_HOME_IN_FRONT: + case STACK_STATE_HOME_TO_BACK: + return mHomeStack; + case STACK_STATE_HOME_TO_FRONT: + case STACK_STATE_HOME_IN_BACK: + default: + return mFocusedStack; + } + } + + boolean isFrontStack(ActivityStack stack) { + return !(stack.isHomeStack() ^ getFocusedStack().isHomeStack()); + } + + void moveHomeStack(boolean toFront) { + final boolean homeInFront = isFrontStack(mHomeStack); + if (homeInFront ^ toFront) { + if (DEBUG_STACK) Slog.d(TAG, "moveHomeTask: mStackState old=" + + stackStateToString(mStackState) + " new=" + stackStateToString(homeInFront ? + STACK_STATE_HOME_TO_BACK : STACK_STATE_HOME_TO_FRONT)); + mStackState = homeInFront ? STACK_STATE_HOME_TO_BACK : STACK_STATE_HOME_TO_FRONT; + } + } + + void moveHomeToTop() { + moveHomeStack(true); + mHomeStack.moveHomeTaskToTop(); + } + + boolean resumeHomeActivity(ActivityRecord prev) { + moveHomeToTop(); + if (prev != null) { + prev.task.mOnTopOfHome = false; + } + ActivityRecord r = mHomeStack.topRunningActivityLocked(null); + if (r != null && r.isHomeActivity()) { + mService.setFocusedActivityLocked(r); + return resumeTopActivitiesLocked(mHomeStack, prev, null); + } + return mService.startHomeActivityLocked(mCurrentUser); + } + + void setDismissKeyguard(boolean dismiss) { + if (ActivityManagerService.DEBUG_LOCKSCREEN) mService.logLockScreen(" dismiss=" + dismiss); + mDismissKeyguardOnNextActivity = dismiss; + } + + TaskRecord anyTaskForIdLocked(int id) { + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + ActivityStack stack = mStacks.get(stackNdx); + TaskRecord task = stack.taskForIdLocked(id); + if (task != null) { + return task; + } + } + return null; + } + + ActivityRecord isInAnyStackLocked(IBinder token) { + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityRecord r = mStacks.get(stackNdx).isInStackLocked(token); + if (r != null) { + return r; + } + } + return null; + } + + int getNextTaskId() { + do { + mCurTaskId++; + if (mCurTaskId <= 0) { + mCurTaskId = 1; + } + } while (anyTaskForIdLocked(mCurTaskId) != null); + return mCurTaskId; + } + + void removeTask(TaskRecord task) { + mWindowManager.removeTask(task.taskId); + final ActivityStack stack = task.stack; + final ActivityRecord r = stack.mResumedActivity; + if (r != null && r.task == task) { + stack.mResumedActivity = null; + } + if (stack.removeTask(task) && !stack.isHomeStack()) { + if (DEBUG_STACK) Slog.i(TAG, "removeTask: removing stack " + stack); + mStacks.remove(stack); + final int stackId = stack.mStackId; + final int nextStackId = mWindowManager.removeStack(stackId); + // TODO: Perhaps we need to let the ActivityManager determine the next focus... + if (mFocusedStack == null || mFocusedStack.mStackId == stackId) { + // If this is the last app stack, set mFocusedStack to null. + mFocusedStack = nextStackId == HOME_STACK_ID ? null : getStack(nextStackId); + } + } + } + + ActivityRecord resumedAppLocked() { + ActivityStack stack = getFocusedStack(); + if (stack == null) { + return null; + } + ActivityRecord resumedActivity = stack.mResumedActivity; + if (resumedActivity == null || resumedActivity.app == null) { + resumedActivity = stack.mPausingActivity; + if (resumedActivity == null || resumedActivity.app == null) { + resumedActivity = stack.topRunningActivityLocked(null); + } + } + return resumedActivity; + } + + boolean attachApplicationLocked(ProcessRecord app, boolean headless) throws Exception { + boolean didSomething = false; + final String processName = app.processName; + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + if (!isFrontStack(stack)) { + continue; + } + ActivityRecord hr = stack.topRunningActivityLocked(null); + if (hr != null) { + if (hr.app == null && app.uid == hr.info.applicationInfo.uid + && processName.equals(hr.processName)) { + try { + if (headless) { + Slog.e(TAG, "Starting activities not supported on headless device: " + + hr); + } else if (realStartActivityLocked(hr, app, true, true)) { + didSomething = true; + } + } catch (Exception e) { + Slog.w(TAG, "Exception in new application when starting activity " + + hr.intent.getComponent().flattenToShortString(), e); + throw e; + } + } + } + } + if (!didSomething) { + ensureActivitiesVisibleLocked(null, 0); + } + return didSomething; + } + + boolean allResumedActivitiesIdle() { + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + if (!isFrontStack(stack)) { + continue; + } + final ActivityRecord resumedActivity = stack.mResumedActivity; + if (resumedActivity == null || !resumedActivity.idle) { + return false; + } + } + return true; + } + + boolean allResumedActivitiesComplete() { + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + if (isFrontStack(stack)) { + final ActivityRecord r = stack.mResumedActivity; + if (r != null && r.state != ActivityState.RESUMED) { + return false; + } + } + } + // TODO: Not sure if this should check if all Paused are complete too. + switch (mStackState) { + case STACK_STATE_HOME_TO_BACK: + if (DEBUG_STACK) Slog.d(TAG, "allResumedActivitiesComplete: mStackState old=" + + stackStateToString(STACK_STATE_HOME_TO_BACK) + " new=" + + stackStateToString(STACK_STATE_HOME_IN_BACK)); + mStackState = STACK_STATE_HOME_IN_BACK; + break; + case STACK_STATE_HOME_TO_FRONT: + if (DEBUG_STACK) Slog.d(TAG, "allResumedActivitiesComplete: mStackState old=" + + stackStateToString(STACK_STATE_HOME_TO_FRONT) + " new=" + + stackStateToString(STACK_STATE_HOME_IN_FRONT)); + mStackState = STACK_STATE_HOME_IN_FRONT; + break; + } + return true; + } + + boolean allResumedActivitiesVisible() { + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + final ActivityRecord r = stack.mResumedActivity; + if (r != null && (!r.nowVisible || r.waitingVisible)) { + return false; + } + } + return true; + } + + /** + * Pause all activities in either all of the stacks or just the back stacks. + * @param userLeaving Passed to pauseActivity() to indicate whether to call onUserLeaving(). + * @return true if any activity was paused as a result of this call. + */ + boolean pauseBackStacks(boolean userLeaving) { + boolean someActivityPaused = false; + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + if (!isFrontStack(stack) && stack.mResumedActivity != null) { + if (DEBUG_STATES) Slog.d(TAG, "pauseBackStacks: stack=" + stack + + " mResumedActivity=" + stack.mResumedActivity); + stack.startPausingLocked(userLeaving, false); + someActivityPaused = true; + } + } + return someActivityPaused; + } + + boolean allPausedActivitiesComplete() { + boolean pausing = true; + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + final ActivityRecord r = stack.mPausingActivity; + if (r != null && r.state != ActivityState.PAUSED + && r.state != ActivityState.STOPPED + && r.state != ActivityState.STOPPING) { + if (DEBUG_STATES) { + Slog.d(TAG, "allPausedActivitiesComplete: r=" + r + " state=" + r.state); + pausing = false; + } else { + return false; + } + } + } + return pausing; + } + + void reportActivityVisibleLocked(ActivityRecord r) { + for (int i = mWaitingActivityVisible.size()-1; i >= 0; i--) { + WaitResult w = mWaitingActivityVisible.get(i); + w.timeout = false; + if (r != null) { + w.who = new ComponentName(r.info.packageName, r.info.name); + } + w.totalTime = SystemClock.uptimeMillis() - w.thisTime; + w.thisTime = w.totalTime; + } + mService.notifyAll(); + dismissKeyguard(); + } + + void reportActivityLaunchedLocked(boolean timeout, ActivityRecord r, + long thisTime, long totalTime) { + for (int i = mWaitingActivityLaunched.size() - 1; i >= 0; i--) { + WaitResult w = mWaitingActivityLaunched.remove(i); + w.timeout = timeout; + if (r != null) { + w.who = new ComponentName(r.info.packageName, r.info.name); + } + w.thisTime = thisTime; + w.totalTime = totalTime; + } + mService.notifyAll(); + } + + ActivityRecord topRunningActivityLocked() { + final ActivityStack focusedStack = getFocusedStack(); + ActivityRecord r = focusedStack.topRunningActivityLocked(null); + if (r != null) { + return r; + } + + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + if (stack != focusedStack && isFrontStack(stack)) { + r = stack.topRunningActivityLocked(null); + if (r != null) { + return r; + } + } + } + return null; + } + + ActivityRecord getTasksLocked(int maxNum, IThumbnailReceiver receiver, + PendingThumbnailsRecord pending, List<RunningTaskInfo> list) { + ActivityRecord r = null; + + // Gather all of the running tasks for each stack into runningTaskLists. + final int numStacks = mStacks.size(); + ArrayList<RunningTaskInfo>[] runningTaskLists = new ArrayList[numStacks]; + for (int stackNdx = numStacks - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + ArrayList<RunningTaskInfo> stackTaskList = new ArrayList<RunningTaskInfo>(); + runningTaskLists[stackNdx] = stackTaskList; + final ActivityRecord ar = stack.getTasksLocked(receiver, pending, stackTaskList); + if (isFrontStack(stack)) { + r = ar; + } + } + + // The lists are already sorted from most recent to oldest. Just pull the most recent off + // each list and add it to list. Stop when all lists are empty or maxNum reached. + while (maxNum > 0) { + long mostRecentActiveTime = Long.MIN_VALUE; + ArrayList<RunningTaskInfo> selectedStackList = null; + for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + ArrayList<RunningTaskInfo> stackTaskList = runningTaskLists[stackNdx]; + if (!stackTaskList.isEmpty()) { + final long lastActiveTime = stackTaskList.get(0).lastActiveTime; + if (lastActiveTime > mostRecentActiveTime) { + mostRecentActiveTime = lastActiveTime; + selectedStackList = stackTaskList; + } + } + } + if (selectedStackList != null) { + list.add(selectedStackList.remove(0)); + --maxNum; + } else { + break; + } + } + + return r; + } + + ActivityInfo resolveActivity(Intent intent, String resolvedType, int startFlags, + String profileFile, ParcelFileDescriptor profileFd, int userId) { + // Collect information about the target of the Intent. + ActivityInfo aInfo; + try { + ResolveInfo rInfo = + AppGlobals.getPackageManager().resolveIntent( + intent, resolvedType, + PackageManager.MATCH_DEFAULT_ONLY + | ActivityManagerService.STOCK_PM_FLAGS, userId); + aInfo = rInfo != null ? rInfo.activityInfo : null; + } catch (RemoteException e) { + aInfo = null; + } + + if (aInfo != null) { + // Store the found target back into the intent, because now that + // we have it we never want to do this again. For example, if the + // user navigates back to this point in the history, we should + // always restart the exact same activity. + intent.setComponent(new ComponentName( + aInfo.applicationInfo.packageName, aInfo.name)); + + // Don't debug things in the system process + if ((startFlags&ActivityManager.START_FLAG_DEBUG) != 0) { + if (!aInfo.processName.equals("system")) { + mService.setDebugApp(aInfo.processName, true, false); + } + } + + if ((startFlags&ActivityManager.START_FLAG_OPENGL_TRACES) != 0) { + if (!aInfo.processName.equals("system")) { + mService.setOpenGlTraceApp(aInfo.applicationInfo, aInfo.processName); + } + } + + if (profileFile != null) { + if (!aInfo.processName.equals("system")) { + mService.setProfileApp(aInfo.applicationInfo, aInfo.processName, + profileFile, profileFd, + (startFlags&ActivityManager.START_FLAG_AUTO_STOP_PROFILER) != 0); + } + } + } + return aInfo; + } + + void startHomeActivity(Intent intent, ActivityInfo aInfo) { + moveHomeToTop(); + startActivityLocked(null, intent, null, aInfo, null, null, 0, 0, 0, null, 0, + null, false, null); + } + + final int startActivityMayWait(IApplicationThread caller, int callingUid, + String callingPackage, Intent intent, String resolvedType, IBinder resultTo, + String resultWho, int requestCode, int startFlags, String profileFile, + ParcelFileDescriptor profileFd, WaitResult outResult, Configuration config, + Bundle options, int userId) { + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + boolean componentSpecified = intent.getComponent() != null; + + // Don't modify the client's object! + intent = new Intent(intent); + + // Collect information about the target of the Intent. + ActivityInfo aInfo = resolveActivity(intent, resolvedType, startFlags, + profileFile, profileFd, userId); + + synchronized (mService) { + int callingPid; + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { + callingPid = Binder.getCallingPid(); + callingUid = Binder.getCallingUid(); + } else { + callingPid = callingUid = -1; + } + + final ActivityStack stack = getFocusedStack(); + stack.mConfigWillChange = config != null + && mService.mConfiguration.diff(config) != 0; + if (DEBUG_CONFIGURATION) Slog.v(TAG, + "Starting activity when config will change = " + stack.mConfigWillChange); + + final long origId = Binder.clearCallingIdentity(); + + if (aInfo != null && + (aInfo.applicationInfo.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { + // This may be a heavy-weight process! Check to see if we already + // have another, different heavy-weight process running. + if (aInfo.processName.equals(aInfo.applicationInfo.packageName)) { + if (mService.mHeavyWeightProcess != null && + (mService.mHeavyWeightProcess.info.uid != aInfo.applicationInfo.uid || + !mService.mHeavyWeightProcess.processName.equals(aInfo.processName))) { + int realCallingUid = callingUid; + if (caller != null) { + ProcessRecord callerApp = mService.getRecordForAppLocked(caller); + if (callerApp != null) { + realCallingUid = callerApp.info.uid; + } else { + Slog.w(TAG, "Unable to find app for caller " + caller + + " (pid=" + callingPid + ") when starting: " + + intent.toString()); + ActivityOptions.abort(options); + return ActivityManager.START_PERMISSION_DENIED; + } + } + + IIntentSender target = mService.getIntentSenderLocked( + ActivityManager.INTENT_SENDER_ACTIVITY, "android", + realCallingUid, userId, null, null, 0, new Intent[] { intent }, + new String[] { resolvedType }, PendingIntent.FLAG_CANCEL_CURRENT + | PendingIntent.FLAG_ONE_SHOT, null); + + Intent newIntent = new Intent(); + if (requestCode >= 0) { + // Caller is requesting a result. + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_HAS_RESULT, true); + } + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_INTENT, + new IntentSender(target)); + if (mService.mHeavyWeightProcess.activities.size() > 0) { + ActivityRecord hist = mService.mHeavyWeightProcess.activities.get(0); + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_APP, + hist.packageName); + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_CUR_TASK, + hist.task.taskId); + } + newIntent.putExtra(HeavyWeightSwitcherActivity.KEY_NEW_APP, + aInfo.packageName); + newIntent.setFlags(intent.getFlags()); + newIntent.setClassName("android", + HeavyWeightSwitcherActivity.class.getName()); + intent = newIntent; + resolvedType = null; + caller = null; + callingUid = Binder.getCallingUid(); + callingPid = Binder.getCallingPid(); + componentSpecified = true; + try { + ResolveInfo rInfo = + AppGlobals.getPackageManager().resolveIntent( + intent, null, + PackageManager.MATCH_DEFAULT_ONLY + | ActivityManagerService.STOCK_PM_FLAGS, userId); + aInfo = rInfo != null ? rInfo.activityInfo : null; + aInfo = mService.getActivityInfoForUser(aInfo, userId); + } catch (RemoteException e) { + aInfo = null; + } + } + } + } + + int res = startActivityLocked(caller, intent, resolvedType, + aInfo, resultTo, resultWho, requestCode, callingPid, callingUid, + callingPackage, startFlags, options, componentSpecified, null); + + if (stack.mConfigWillChange) { + // If the caller also wants to switch to a new configuration, + // do so now. This allows a clean switch, as we are waiting + // for the current activity to pause (so we will not destroy + // it), and have not yet started the next activity. + mService.enforceCallingPermission(android.Manifest.permission.CHANGE_CONFIGURATION, + "updateConfiguration()"); + stack.mConfigWillChange = false; + if (DEBUG_CONFIGURATION) Slog.v(TAG, + "Updating to new configuration after starting activity."); + mService.updateConfigurationLocked(config, null, false, false); + } + + Binder.restoreCallingIdentity(origId); + + if (outResult != null) { + outResult.result = res; + if (res == ActivityManager.START_SUCCESS) { + mWaitingActivityLaunched.add(outResult); + do { + try { + mService.wait(); + } catch (InterruptedException e) { + } + } while (!outResult.timeout && outResult.who == null); + } else if (res == ActivityManager.START_TASK_TO_FRONT) { + ActivityRecord r = stack.topRunningActivityLocked(null); + if (r.nowVisible) { + outResult.timeout = false; + outResult.who = new ComponentName(r.info.packageName, r.info.name); + outResult.totalTime = 0; + outResult.thisTime = 0; + } else { + outResult.thisTime = SystemClock.uptimeMillis(); + mWaitingActivityVisible.add(outResult); + do { + try { + mService.wait(); + } catch (InterruptedException e) { + } + } while (!outResult.timeout && outResult.who == null); + } + } + } + + return res; + } + } + + final int startActivities(IApplicationThread caller, int callingUid, String callingPackage, + Intent[] intents, String[] resolvedTypes, IBinder resultTo, + Bundle options, int userId) { + if (intents == null) { + throw new NullPointerException("intents is null"); + } + if (resolvedTypes == null) { + throw new NullPointerException("resolvedTypes is null"); + } + if (intents.length != resolvedTypes.length) { + throw new IllegalArgumentException("intents are length different than resolvedTypes"); + } + + + int callingPid; + if (callingUid >= 0) { + callingPid = -1; + } else if (caller == null) { + callingPid = Binder.getCallingPid(); + callingUid = Binder.getCallingUid(); + } else { + callingPid = callingUid = -1; + } + final long origId = Binder.clearCallingIdentity(); + try { + synchronized (mService) { + ActivityRecord[] outActivity = new ActivityRecord[1]; + for (int i=0; i<intents.length; i++) { + Intent intent = intents[i]; + if (intent == null) { + continue; + } + + // Refuse possible leaked file descriptors + if (intent != null && intent.hasFileDescriptors()) { + throw new IllegalArgumentException("File descriptors passed in Intent"); + } + + boolean componentSpecified = intent.getComponent() != null; + + // Don't modify the client's object! + intent = new Intent(intent); + + // Collect information about the target of the Intent. + ActivityInfo aInfo = resolveActivity(intent, resolvedTypes[i], + 0, null, null, userId); + // TODO: New, check if this is correct + aInfo = mService.getActivityInfoForUser(aInfo, userId); + + if (aInfo != null && + (aInfo.applicationInfo.flags & ApplicationInfo.FLAG_CANT_SAVE_STATE) + != 0) { + throw new IllegalArgumentException( + "FLAG_CANT_SAVE_STATE not supported here"); + } + + Bundle theseOptions; + if (options != null && i == intents.length-1) { + theseOptions = options; + } else { + theseOptions = null; + } + int res = startActivityLocked(caller, intent, resolvedTypes[i], + aInfo, resultTo, null, -1, callingPid, callingUid, callingPackage, + 0, theseOptions, componentSpecified, outActivity); + if (res < 0) { + return res; + } + + resultTo = outActivity[0] != null ? outActivity[0].appToken : null; + } + } + } finally { + Binder.restoreCallingIdentity(origId); + } + + return ActivityManager.START_SUCCESS; + } + + final boolean realStartActivityLocked(ActivityRecord r, + ProcessRecord app, boolean andResume, boolean checkConfig) + throws RemoteException { + + r.startFreezingScreenLocked(app, 0); + if (false) Slog.d(TAG, "realStartActivity: setting app visibility true"); + mWindowManager.setAppVisibility(r.appToken, true); + + // schedule launch ticks to collect information about slow apps. + r.startLaunchTickingLocked(); + + // Have the window manager re-evaluate the orientation of + // the screen based on the new activity order. Note that + // as a result of this, it can call back into the activity + // manager with a new orientation. We don't care about that, + // because the activity is not currently running so we are + // just restarting it anyway. + if (checkConfig) { + Configuration config = mWindowManager.updateOrientationFromAppTokens( + mService.mConfiguration, + r.mayFreezeScreenLocked(app) ? r.appToken : null); + mService.updateConfigurationLocked(config, r, false, false); + } + + r.app = app; + app.waitingToKill = null; + r.launchCount++; + r.lastLaunchTime = SystemClock.uptimeMillis(); + + if (localLOGV) Slog.v(TAG, "Launching: " + r); + + int idx = app.activities.indexOf(r); + if (idx < 0) { + app.activities.add(r); + } + mService.updateLruProcessLocked(app, true, true); + + final ActivityStack stack = r.task.stack; + try { + if (app.thread == null) { + throw new RemoteException(); + } + List<ResultInfo> results = null; + List<Intent> newIntents = null; + if (andResume) { + results = r.results; + newIntents = r.newIntents; + } + if (DEBUG_SWITCH) Slog.v(TAG, "Launching: " + r + + " icicle=" + r.icicle + + " with results=" + results + " newIntents=" + newIntents + + " andResume=" + andResume); + if (andResume) { + EventLog.writeEvent(EventLogTags.AM_RESTART_ACTIVITY, + r.userId, System.identityHashCode(r), + r.task.taskId, r.shortComponentName); + } + if (r.isHomeActivity() && r.isNotResolverActivity()) { + // Home process is the root process of the task. + mService.mHomeProcess = r.task.mActivities.get(0).app; + } + mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName()); + r.sleeping = false; + r.forceNewConfig = false; + mService.showAskCompatModeDialogLocked(r); + r.compat = mService.compatibilityInfoForPackageLocked(r.info.applicationInfo); + String profileFile = null; + ParcelFileDescriptor profileFd = null; + boolean profileAutoStop = false; + if (mService.mProfileApp != null && mService.mProfileApp.equals(app.processName)) { + if (mService.mProfileProc == null || mService.mProfileProc == app) { + mService.mProfileProc = app; + profileFile = mService.mProfileFile; + profileFd = mService.mProfileFd; + profileAutoStop = mService.mAutoStopProfiler; + } + } + app.hasShownUi = true; + app.pendingUiClean = true; + if (profileFd != null) { + try { + profileFd = profileFd.dup(); + } catch (IOException e) { + if (profileFd != null) { + try { + profileFd.close(); + } catch (IOException o) { + } + profileFd = null; + } + } + } + app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_TOP); + app.thread.scheduleLaunchActivity(new Intent(r.intent), r.appToken, + System.identityHashCode(r), r.info, + new Configuration(mService.mConfiguration), r.compat, + app.repProcState, r.icicle, results, newIntents, !andResume, + mService.isNextTransitionForward(), profileFile, profileFd, + profileAutoStop); + + if ((app.info.flags&ApplicationInfo.FLAG_CANT_SAVE_STATE) != 0) { + // This may be a heavy-weight process! Note that the package + // manager will ensure that only activity can run in the main + // process of the .apk, which is the only thing that will be + // considered heavy-weight. + if (app.processName.equals(app.info.packageName)) { + if (mService.mHeavyWeightProcess != null + && mService.mHeavyWeightProcess != app) { + Slog.w(TAG, "Starting new heavy weight process " + app + + " when already running " + + mService.mHeavyWeightProcess); + } + mService.mHeavyWeightProcess = app; + Message msg = mService.mHandler.obtainMessage( + ActivityManagerService.POST_HEAVY_NOTIFICATION_MSG); + msg.obj = r; + mService.mHandler.sendMessage(msg); + } + } + + } catch (RemoteException e) { + if (r.launchFailed) { + // This is the second time we failed -- finish activity + // and give up. + Slog.e(TAG, "Second failure launching " + + r.intent.getComponent().flattenToShortString() + + ", giving up", e); + mService.appDiedLocked(app, app.pid, app.thread); + stack.requestFinishActivityLocked(r.appToken, Activity.RESULT_CANCELED, null, + "2nd-crash", false); + return false; + } + + // This is the first time we failed -- restart process and + // retry. + app.activities.remove(r); + throw e; + } + + r.launchFailed = false; + if (stack.updateLRUListLocked(r)) { + Slog.w(TAG, "Activity " + r + + " being launched, but already in LRU list"); + } + + if (andResume) { + // As part of the process of launching, ActivityThread also performs + // a resume. + stack.minimalResumeActivityLocked(r); + } else { + // This activity is not starting in the resumed state... which + // should look like we asked it to pause+stop (but remain visible), + // and it has done so and reported back the current icicle and + // other state. + if (DEBUG_STATES) Slog.v(TAG, "Moving to STOPPED: " + r + + " (starting in stopped state)"); + r.state = ActivityState.STOPPED; + r.stopped = true; + } + + // Launch the new version setup screen if needed. We do this -after- + // launching the initial activity (that is, home), so that it can have + // a chance to initialize itself while in the background, making the + // switch back to it faster and look better. + if (isFrontStack(stack)) { + mService.startSetupActivityLocked(); + } + + return true; + } + + void startSpecificActivityLocked(ActivityRecord r, + boolean andResume, boolean checkConfig) { + // Is this activity's application already running? + ProcessRecord app = mService.getProcessRecordLocked(r.processName, + r.info.applicationInfo.uid, true); + + r.task.stack.setLaunchTime(r); + + if (app != null && app.thread != null) { + try { + app.addPackage(r.info.packageName, mService.mProcessStats); + realStartActivityLocked(r, app, andResume, checkConfig); + return; + } catch (RemoteException e) { + Slog.w(TAG, "Exception when starting activity " + + r.intent.getComponent().flattenToShortString(), e); + } + + // If a dead object exception was thrown -- fall through to + // restart the application. + } + + mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0, + "activity", r.intent.getComponent(), false, false, true); + } + + final int startActivityLocked(IApplicationThread caller, + Intent intent, String resolvedType, ActivityInfo aInfo, IBinder resultTo, + String resultWho, int requestCode, + int callingPid, int callingUid, String callingPackage, int startFlags, Bundle options, + boolean componentSpecified, ActivityRecord[] outActivity) { + int err = ActivityManager.START_SUCCESS; + + ProcessRecord callerApp = null; + if (caller != null) { + callerApp = mService.getRecordForAppLocked(caller); + if (callerApp != null) { + callingPid = callerApp.pid; + callingUid = callerApp.info.uid; + } else { + Slog.w(TAG, "Unable to find app for caller " + caller + + " (pid=" + callingPid + ") when starting: " + + intent.toString()); + err = ActivityManager.START_PERMISSION_DENIED; + } + } + + if (err == ActivityManager.START_SUCCESS) { + final int userId = aInfo != null ? UserHandle.getUserId(aInfo.applicationInfo.uid) : 0; + Slog.i(TAG, "START u" + userId + " {" + intent.toShortString(true, true, true, false) + + "} from pid " + (callerApp != null ? callerApp.pid : callingPid)); + } + + ActivityRecord sourceRecord = null; + ActivityRecord resultRecord = null; + if (resultTo != null) { + sourceRecord = isInAnyStackLocked(resultTo); + if (DEBUG_RESULTS) Slog.v( + TAG, "Will send result to " + resultTo + " " + sourceRecord); + if (sourceRecord != null) { + if (requestCode >= 0 && !sourceRecord.finishing) { + resultRecord = sourceRecord; + } + } + } + ActivityStack resultStack = resultRecord == null ? null : resultRecord.task.stack; + + int launchFlags = intent.getFlags(); + + if ((launchFlags&Intent.FLAG_ACTIVITY_FORWARD_RESULT) != 0 + && sourceRecord != null) { + // Transfer the result target from the source activity to the new + // one being started, including any failures. + if (requestCode >= 0) { + ActivityOptions.abort(options); + return ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT; + } + resultRecord = sourceRecord.resultTo; + resultWho = sourceRecord.resultWho; + requestCode = sourceRecord.requestCode; + sourceRecord.resultTo = null; + if (resultRecord != null) { + resultRecord.removeResultsLocked( + sourceRecord, resultWho, requestCode); + } + } + + if (err == ActivityManager.START_SUCCESS && intent.getComponent() == null) { + // We couldn't find a class that can handle the given Intent. + // That's the end of that! + err = ActivityManager.START_INTENT_NOT_RESOLVED; + } + + if (err == ActivityManager.START_SUCCESS && aInfo == null) { + // We couldn't find the specific class specified in the Intent. + // Also the end of the line. + err = ActivityManager.START_CLASS_NOT_FOUND; + } + + if (err != ActivityManager.START_SUCCESS) { + if (resultRecord != null) { + resultStack.sendActivityResultLocked(-1, + resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + setDismissKeyguard(false); + ActivityOptions.abort(options); + return err; + } + + final int startAnyPerm = mService.checkPermission( + START_ANY_ACTIVITY, callingPid, callingUid); + final int componentPerm = mService.checkComponentPermission(aInfo.permission, callingPid, + callingUid, aInfo.applicationInfo.uid, aInfo.exported); + if (startAnyPerm != PERMISSION_GRANTED && componentPerm != PERMISSION_GRANTED) { + if (resultRecord != null) { + resultStack.sendActivityResultLocked(-1, + resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + setDismissKeyguard(false); + String msg; + if (!aInfo.exported) { + msg = "Permission Denial: starting " + intent.toString() + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ")" + + " not exported from uid " + aInfo.applicationInfo.uid; + } else { + msg = "Permission Denial: starting " + intent.toString() + + " from " + callerApp + " (pid=" + callingPid + + ", uid=" + callingUid + ")" + + " requires " + aInfo.permission; + } + Slog.w(TAG, msg); + throw new SecurityException(msg); + } + + boolean abort = !mService.mIntentFirewall.checkStartActivity(intent, callingUid, + callingPid, resolvedType, aInfo.applicationInfo); + + if (mService.mController != null) { + try { + // The Intent we give to the watcher has the extra data + // stripped off, since it can contain private information. + Intent watchIntent = intent.cloneFilter(); + abort |= !mService.mController.activityStarting(watchIntent, + aInfo.applicationInfo.packageName); + } catch (RemoteException e) { + mService.mController = null; + } + } + + if (abort) { + if (resultRecord != null) { + resultStack.sendActivityResultLocked(-1, resultRecord, resultWho, requestCode, + Activity.RESULT_CANCELED, null); + } + // We pretend to the caller that it was really started, but + // they will just get a cancel result. + setDismissKeyguard(false); + ActivityOptions.abort(options); + return ActivityManager.START_SUCCESS; + } + + ActivityRecord r = new ActivityRecord(mService, callerApp, callingUid, callingPackage, + intent, resolvedType, aInfo, mService.mConfiguration, + resultRecord, resultWho, requestCode, componentSpecified, this); + if (outActivity != null) { + outActivity[0] = r; + } + + final ActivityStack stack = getFocusedStack(); + if (stack.mResumedActivity == null + || stack.mResumedActivity.info.applicationInfo.uid != callingUid) { + if (!mService.checkAppSwitchAllowedLocked(callingPid, callingUid, "Activity start")) { + PendingActivityLaunch pal = + new PendingActivityLaunch(r, sourceRecord, startFlags, stack); + mService.mPendingActivityLaunches.add(pal); + setDismissKeyguard(false); + ActivityOptions.abort(options); + return ActivityManager.START_SWITCHES_CANCELED; + } + } + + if (mService.mDidAppSwitch) { + // This is the second allowed switch since we stopped switches, + // so now just generally allow switches. Use case: user presses + // home (switches disabled, switch to home, mDidAppSwitch now true); + // user taps a home icon (coming from home so allowed, we hit here + // and now allow anyone to switch again). + mService.mAppSwitchesAllowedTime = 0; + } else { + mService.mDidAppSwitch = true; + } + + mService.doPendingActivityLaunchesLocked(false); + + err = startActivityUncheckedLocked(r, sourceRecord, startFlags, true, options); + + if (allPausedActivitiesComplete()) { + // If someone asked to have the keyguard dismissed on the next + // activity start, but we are not actually doing an activity + // switch... just dismiss the keyguard now, because we + // probably want to see whatever is behind it. + dismissKeyguard(); + } + return err; + } + + ActivityStack adjustStackFocus(ActivityRecord r) { + final TaskRecord task = r.task; + if (r.isApplicationActivity() || (task != null && task.isApplicationTask())) { + if (task != null) { + if (mFocusedStack != task.stack) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, + "adjustStackFocus: Setting focused stack to r=" + r + " task=" + task); + mFocusedStack = task.stack; + } else { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, + "adjustStackFocus: Focused stack already=" + mFocusedStack); + } + return mFocusedStack; + } + + if (mFocusedStack != null) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, + "adjustStackFocus: Have a focused stack=" + mFocusedStack); + return mFocusedStack; + } + + for (int stackNdx = mStacks.size() - 1; stackNdx > 0; --stackNdx) { + ActivityStack stack = mStacks.get(stackNdx); + if (!stack.isHomeStack()) { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, + "adjustStackFocus: Setting focused stack=" + stack); + mFocusedStack = stack; + return mFocusedStack; + } + } + + // Time to create the first app stack for this user. + int stackId = mService.createStack(-1, HOME_STACK_ID, + StackBox.TASK_STACK_GOES_OVER, 1.0f); + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, "adjustStackFocus: New stack r=" + r + + " stackId=" + stackId); + mFocusedStack = getStack(stackId); + return mFocusedStack; + } + return mHomeStack; + } + + void setFocusedStack(ActivityRecord r) { + if (r == null) { + return; + } + if (!r.isApplicationActivity() || (r.task != null && !r.task.isApplicationTask())) { + if (mStackState != STACK_STATE_HOME_IN_FRONT) { + if (DEBUG_STACK || DEBUG_FOCUS) Slog.d(TAG, "setFocusedStack: mStackState old=" + + stackStateToString(mStackState) + " new=" + + stackStateToString(STACK_STATE_HOME_TO_FRONT) + + " Callers=" + Debug.getCallers(3)); + mStackState = STACK_STATE_HOME_TO_FRONT; + } + } else { + if (DEBUG_FOCUS || DEBUG_STACK) Slog.d(TAG, + "setFocusedStack: Setting focused stack to r=" + r + " task=" + r.task + + " Callers=" + Debug.getCallers(3)); + mFocusedStack = r.task.stack; + if (mStackState != STACK_STATE_HOME_IN_BACK) { + if (DEBUG_STACK) Slog.d(TAG, "setFocusedStack: mStackState old=" + + stackStateToString(mStackState) + " new=" + + stackStateToString(STACK_STATE_HOME_TO_BACK) + + " Callers=" + Debug.getCallers(3)); + mStackState = STACK_STATE_HOME_TO_BACK; + } + } + } + + final int startActivityUncheckedLocked(ActivityRecord r, + ActivityRecord sourceRecord, int startFlags, boolean doResume, + Bundle options) { + final Intent intent = r.intent; + final int callingUid = r.launchedFromUid; + + int launchFlags = intent.getFlags(); + + // We'll invoke onUserLeaving before onPause only if the launching + // activity did not explicitly state that this is an automated launch. + mUserLeaving = (launchFlags&Intent.FLAG_ACTIVITY_NO_USER_ACTION) == 0; + if (DEBUG_USER_LEAVING) Slog.v(TAG, "startActivity() => mUserLeaving=" + mUserLeaving); + + // If the caller has asked not to resume at this point, we make note + // of this in the record so that we can skip it when trying to find + // the top running activity. + if (!doResume) { + r.delayedResume = true; + } + + ActivityRecord notTop = (launchFlags&Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP) != 0 ? r : null; + + // If the onlyIfNeeded flag is set, then we can do this if the activity + // being launched is the same as the one making the call... or, as + // a special case, if we do not know the caller then we count the + // current top activity as the caller. + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + ActivityRecord checkedCaller = sourceRecord; + if (checkedCaller == null) { + checkedCaller = getFocusedStack().topRunningNonDelayedActivityLocked(notTop); + } + if (!checkedCaller.realActivity.equals(r.realActivity)) { + // Caller is not the same as launcher, so always needed. + startFlags &= ~ActivityManager.START_FLAG_ONLY_IF_NEEDED; + } + } + + if (sourceRecord == null) { + // This activity is not being started from another... in this + // case we -always- start a new task. + if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { + Slog.w(TAG, "startActivity called from non-Activity context; forcing " + + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } + } else if (sourceRecord.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + // The original activity who is starting us is running as a single + // instance... this new activity it is starting must go on its + // own task. + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } else if (r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { + // The activity being started is a single instance... it always + // gets launched into its own task. + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } + + final ActivityStack sourceStack; + if (sourceRecord != null) { + if (sourceRecord.finishing) { + // If the source is finishing, we can't further count it as our source. This + // is because the task it is associated with may now be empty and on its way out, + // so we don't want to blindly throw it in to that task. Instead we will take + // the NEW_TASK flow and try to find a task for it. + if ((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) == 0) { + Slog.w(TAG, "startActivity called from finishing " + sourceRecord + + "; forcing " + "Intent.FLAG_ACTIVITY_NEW_TASK for: " + intent); + launchFlags |= Intent.FLAG_ACTIVITY_NEW_TASK; + } + sourceRecord = null; + sourceStack = null; + } else { + sourceStack = sourceRecord.task.stack; + } + } else { + sourceStack = null; + } + + if (r.resultTo != null && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { + // For whatever reason this activity is being launched into a new + // task... yet the caller has requested a result back. Well, that + // is pretty messed up, so instead immediately send back a cancel + // and let the new task continue launched as normal without a + // dependency on its originator. + Slog.w(TAG, "Activity is launching as a new task, so cancelling activity result."); + r.resultTo.task.stack.sendActivityResultLocked(-1, + r.resultTo, r.resultWho, r.requestCode, + Activity.RESULT_CANCELED, null); + r.resultTo = null; + } + + boolean addingToTask = false; + boolean movedHome = false; + TaskRecord reuseTask = null; + ActivityStack targetStack; + if (((launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0 && + (launchFlags&Intent.FLAG_ACTIVITY_MULTIPLE_TASK) == 0) + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + // If bring to front is requested, and no result is requested, and + // we can find a task that was started with this same + // component, then instead of launching bring that one to the front. + if (r.resultTo == null) { + // See if there is a task to bring to the front. If this is + // a SINGLE_INSTANCE activity, there can be one and only one + // instance of it in the history, and it is always in its own + // unique task, so we do a special search. + ActivityRecord intentActivity = r.launchMode != ActivityInfo.LAUNCH_SINGLE_INSTANCE + ? findTaskLocked(r) + : findActivityLocked(intent, r.info); + if (intentActivity != null) { + if (r.task == null) { + r.task = intentActivity.task; + } + targetStack = intentActivity.task.stack; + targetStack.mLastPausedActivity = null; + if (DEBUG_TASKS) Slog.d(TAG, "Bring to front target: " + targetStack + + " from " + intentActivity); + moveHomeStack(targetStack.isHomeStack()); + if (intentActivity.task.intent == null) { + // This task was started because of movement of + // the activity based on affinity... now that we + // are actually launching it, we can assign the + // base intent. + intentActivity.task.setIntent(intent, r.info); + } + // If the target task is not in the front, then we need + // to bring it to the front... except... well, with + // SINGLE_TASK_LAUNCH it's not entirely clear. We'd like + // to have the same behavior as if a new instance was + // being started, which means not bringing it to the front + // if the caller is not itself in the front. + final ActivityStack lastStack = getLastStack(); + ActivityRecord curTop = lastStack == null? + null : lastStack.topRunningNonDelayedActivityLocked(notTop); + if (curTop != null && (curTop.task != intentActivity.task || + curTop.task != lastStack.topTask())) { + r.intent.addFlags(Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT); + if (sourceRecord == null || (sourceStack.topActivity() != null && + sourceStack.topActivity().task == sourceRecord.task)) { + // We really do want to push this one into the + // user's face, right now. + movedHome = true; + if ((launchFlags & + (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) + == (FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_TASK_ON_HOME)) { + // Caller wants to appear on home activity. + intentActivity.task.mOnTopOfHome = true; + } + targetStack.moveTaskToFrontLocked(intentActivity.task, r, options); + options = null; + } + } + // If the caller has requested that the target task be + // reset, then do so. + if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) != 0) { + intentActivity = targetStack.resetTaskIfNeededLocked(intentActivity, r); + } + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + // We don't need to start a new activity, and + // the client said not to do anything if that + // is the case, so this is it! And for paranoia, make + // sure we have correctly resumed the top activity. + if (doResume) { + resumeTopActivitiesLocked(targetStack, null, options); + } else { + ActivityOptions.abort(options); + } + if (r.task == null) Slog.v(TAG, + "startActivityUncheckedLocked: task left null", + new RuntimeException("here").fillInStackTrace()); + return ActivityManager.START_RETURN_INTENT_TO_CALLER; + } + if ((launchFlags & + (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) + == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_CLEAR_TASK)) { + // The caller has requested to completely replace any + // existing task with its new activity. Well that should + // not be too hard... + reuseTask = intentActivity.task; + reuseTask.performClearTaskLocked(); + reuseTask.setIntent(r.intent, r.info); + } else if ((launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0 + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_INSTANCE) { + // In this situation we want to remove all activities + // from the task up to the one being started. In most + // cases this means we are resetting the task to its + // initial state. + ActivityRecord top = + intentActivity.task.performClearTaskLocked(r, launchFlags); + if (top != null) { + if (top.frontOfTask) { + // Activity aliases may mean we use different + // intents for the top activity, so make sure + // the task now has the identity of the new + // intent. + top.task.setIntent(r.intent, r.info); + } + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, + r, top.task); + top.deliverNewIntentLocked(callingUid, r.intent); + } else { + // A special case: we need to + // start the activity because it is not currently + // running, and the caller has asked to clear the + // current task to have this activity at the top. + addingToTask = true; + // Now pretend like this activity is being started + // by the top of its task, so it is put in the + // right place. + sourceRecord = intentActivity; + } + } else if (r.realActivity.equals(intentActivity.task.realActivity)) { + // In this case the top activity on the task is the + // same as the one being launched, so we take that + // as a request to bring the task to the foreground. + // If the top activity in the task is the root + // activity, deliver this new intent to it if it + // desires. + if (((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP) + && intentActivity.realActivity.equals(r.realActivity)) { + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, + intentActivity.task); + if (intentActivity.frontOfTask) { + intentActivity.task.setIntent(r.intent, r.info); + } + intentActivity.deliverNewIntentLocked(callingUid, r.intent); + } else if (!r.intent.filterEquals(intentActivity.task.intent)) { + // In this case we are launching the root activity + // of the task, but with a different intent. We + // should start a new instance on top. + addingToTask = true; + sourceRecord = intentActivity; + } + } else if ((launchFlags&Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED) == 0) { + // In this case an activity is being launched in to an + // existing task, without resetting that task. This + // is typically the situation of launching an activity + // from a notification or shortcut. We want to place + // the new activity on top of the current task. + addingToTask = true; + sourceRecord = intentActivity; + } else if (!intentActivity.task.rootWasReset) { + // In this case we are launching in to an existing task + // that has not yet been started from its front door. + // The current task has been brought to the front. + // Ideally, we'd probably like to place this new task + // at the bottom of its stack, but that's a little hard + // to do with the current organization of the code so + // for now we'll just drop it. + intentActivity.task.setIntent(r.intent, r.info); + } + if (!addingToTask && reuseTask == null) { + // We didn't do anything... but it was needed (a.k.a., client + // don't use that intent!) And for paranoia, make + // sure we have correctly resumed the top activity. + if (doResume) { + targetStack.resumeTopActivityLocked(null, options); + } else { + ActivityOptions.abort(options); + } + if (r.task == null) Slog.v(TAG, + "startActivityUncheckedLocked: task left null", + new RuntimeException("here").fillInStackTrace()); + return ActivityManager.START_TASK_TO_FRONT; + } + } + } + } + + //String uri = r.intent.toURI(); + //Intent intent2 = new Intent(uri); + //Slog.i(TAG, "Given intent: " + r.intent); + //Slog.i(TAG, "URI is: " + uri); + //Slog.i(TAG, "To intent: " + intent2); + + if (r.packageName != null) { + // If the activity being launched is the same as the one currently + // at the top, then we need to check if it should only be launched + // once. + ActivityStack topStack = getFocusedStack(); + ActivityRecord top = topStack.topRunningNonDelayedActivityLocked(notTop); + if (top != null && r.resultTo == null) { + if (top.realActivity.equals(r.realActivity) && top.userId == r.userId) { + if (top.app != null && top.app.thread != null) { + if ((launchFlags&Intent.FLAG_ACTIVITY_SINGLE_TOP) != 0 + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TOP + || r.launchMode == ActivityInfo.LAUNCH_SINGLE_TASK) { + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, top, + top.task); + // For paranoia, make sure we have correctly + // resumed the top activity. + topStack.mLastPausedActivity = null; + if (doResume) { + resumeTopActivitiesLocked(); + } + ActivityOptions.abort(options); + if ((startFlags&ActivityManager.START_FLAG_ONLY_IF_NEEDED) != 0) { + // We don't need to start a new activity, and + // the client said not to do anything if that + // is the case, so this is it! + if (r.task == null) Slog.v(TAG, + "startActivityUncheckedLocked: task left null", + new RuntimeException("here").fillInStackTrace()); + return ActivityManager.START_RETURN_INTENT_TO_CALLER; + } + top.deliverNewIntentLocked(callingUid, r.intent); + if (r.task == null) Slog.v(TAG, + "startActivityUncheckedLocked: task left null", + new RuntimeException("here").fillInStackTrace()); + return ActivityManager.START_DELIVERED_TO_TOP; + } + } + } + } + + } else { + if (r.resultTo != null) { + r.resultTo.task.stack.sendActivityResultLocked(-1, r.resultTo, r.resultWho, + r.requestCode, Activity.RESULT_CANCELED, null); + } + ActivityOptions.abort(options); + if (r.task == null) Slog.v(TAG, + "startActivityUncheckedLocked: task left null", + new RuntimeException("here").fillInStackTrace()); + return ActivityManager.START_CLASS_NOT_FOUND; + } + + boolean newTask = false; + boolean keepCurTransition = false; + + // Should this be considered a new task? + if (r.resultTo == null && !addingToTask + && (launchFlags&Intent.FLAG_ACTIVITY_NEW_TASK) != 0) { + targetStack = adjustStackFocus(r); + moveHomeStack(targetStack.isHomeStack()); + if (reuseTask == null) { + r.setTask(targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true), + null, true); + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + " in new task " + + r.task); + } else { + r.setTask(reuseTask, reuseTask, true); + } + newTask = true; + if (!movedHome) { + if ((launchFlags & + (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) + == (Intent.FLAG_ACTIVITY_NEW_TASK|Intent.FLAG_ACTIVITY_TASK_ON_HOME)) { + // Caller wants to appear on home activity, so before starting + // their own activity we will bring home to the front. + r.task.mOnTopOfHome = true; + } + } + } else if (sourceRecord != null) { + TaskRecord sourceTask = sourceRecord.task; + targetStack = sourceTask.stack; + moveHomeStack(targetStack.isHomeStack()); + if (!addingToTask && + (launchFlags&Intent.FLAG_ACTIVITY_CLEAR_TOP) != 0) { + // In this case, we are adding the activity to an existing + // task, but the caller has asked to clear that task if the + // activity is already running. + ActivityRecord top = sourceTask.performClearTaskLocked(r, launchFlags); + keepCurTransition = true; + if (top != null) { + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, top.task); + top.deliverNewIntentLocked(callingUid, r.intent); + // For paranoia, make sure we have correctly + // resumed the top activity. + targetStack.mLastPausedActivity = null; + if (doResume) { + targetStack.resumeTopActivityLocked(null); + } + ActivityOptions.abort(options); + if (r.task == null) Slog.w(TAG, + "startActivityUncheckedLocked: task left null", + new RuntimeException("here").fillInStackTrace()); + return ActivityManager.START_DELIVERED_TO_TOP; + } + } else if (!addingToTask && + (launchFlags&Intent.FLAG_ACTIVITY_REORDER_TO_FRONT) != 0) { + // In this case, we are launching an activity in our own task + // that may already be running somewhere in the history, and + // we want to shuffle it to the front of the stack if so. + final ActivityRecord top = sourceTask.findActivityInHistoryLocked(r); + if (top != null) { + final TaskRecord task = top.task; + task.moveActivityToFrontLocked(top); + ActivityStack.logStartActivity(EventLogTags.AM_NEW_INTENT, r, task); + top.updateOptionsLocked(options); + top.deliverNewIntentLocked(callingUid, r.intent); + targetStack.mLastPausedActivity = null; + if (doResume) { + targetStack.resumeTopActivityLocked(null); + } + return ActivityManager.START_DELIVERED_TO_TOP; + } + } + // An existing activity is starting this new activity, so we want + // to keep the new one in the same task as the one that is starting + // it. + r.setTask(sourceTask, sourceRecord.thumbHolder, false); + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + + " in existing task " + r.task + " from source " + sourceRecord); + + } else { + // This not being started from an existing activity, and not part + // of a new task... just put it in the top task, though these days + // this case should never happen. + targetStack = adjustStackFocus(r); + moveHomeStack(targetStack.isHomeStack()); + ActivityRecord prev = targetStack.topActivity(); + r.setTask(prev != null ? prev.task + : targetStack.createTaskRecord(getNextTaskId(), r.info, intent, true), + null, true); + if (DEBUG_TASKS) Slog.v(TAG, "Starting new activity " + r + + " in new guessed " + r.task); + } + + mService.grantUriPermissionFromIntentLocked(callingUid, r.packageName, + intent, r.getUriPermissionsLocked()); + + if (newTask) { + EventLog.writeEvent(EventLogTags.AM_CREATE_TASK, r.userId, r.task.taskId); + } + ActivityStack.logStartActivity(EventLogTags.AM_CREATE_ACTIVITY, r, r.task); + targetStack.mLastPausedActivity = null; + targetStack.startActivityLocked(r, newTask, doResume, keepCurTransition, options); + mService.setFocusedActivityLocked(r); + return ActivityManager.START_SUCCESS; + } + + void acquireLaunchWakelock() { + if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) { + throw new IllegalStateException("Calling must be system uid"); + } + mLaunchingActivity.acquire(); + if (!mHandler.hasMessages(LAUNCH_TIMEOUT_MSG)) { + // To be safe, don't allow the wake lock to be held for too long. + mHandler.sendEmptyMessageDelayed(LAUNCH_TIMEOUT_MSG, LAUNCH_TIMEOUT); + } + } + + // Checked. + final ActivityRecord activityIdleInternalLocked(final IBinder token, boolean fromTimeout, + Configuration config) { + if (localLOGV) Slog.v(TAG, "Activity idle: " + token); + + ArrayList<ActivityRecord> stops = null; + ArrayList<ActivityRecord> finishes = null; + ArrayList<UserStartedState> startingUsers = null; + int NS = 0; + int NF = 0; + IApplicationThread sendThumbnail = null; + boolean booting = false; + boolean enableScreen = false; + boolean activityRemoved = false; + + ActivityRecord r = ActivityRecord.forToken(token); + if (r != null) { + if (DEBUG_IDLE) Slog.d(TAG, "activityIdleInternalLocked: Callers=" + + Debug.getCallers(4)); + mHandler.removeMessages(IDLE_TIMEOUT_MSG, r); + r.finishLaunchTickingLocked(); + if (fromTimeout) { + reportActivityLaunchedLocked(fromTimeout, r, -1, -1); + } + + // 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; + } + + // We are now idle. If someone is waiting for a thumbnail from + // us, we can now deliver. + r.idle = true; + + if (r.thumbnailNeeded && r.app != null && r.app.thread != null) { + sendThumbnail = r.app.thread; + r.thumbnailNeeded = false; + } + + //Slog.i(TAG, "IDLE: mBooted=" + mBooted + ", fromTimeout=" + fromTimeout); + if (!mService.mBooted && isFrontStack(r.task.stack)) { + mService.mBooted = true; + enableScreen = true; + } + } + + if (allResumedActivitiesIdle()) { + if (r != null) { + mService.scheduleAppGcsLocked(); + } + + if (mLaunchingActivity.isHeld()) { + mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); + if (VALIDATE_WAKE_LOCK_CALLER && + Binder.getCallingUid() != Process.myUid()) { + throw new IllegalStateException("Calling must be system uid"); + } + mLaunchingActivity.release(); + } + ensureActivitiesVisibleLocked(null, 0); + } + + // Atomically retrieve all of the other things to do. + stops = processStoppingActivitiesLocked(true); + NS = stops != null ? stops.size() : 0; + if ((NF=mFinishingActivities.size()) > 0) { + finishes = new ArrayList<ActivityRecord>(mFinishingActivities); + mFinishingActivities.clear(); + } + + final ArrayList<ActivityRecord> thumbnails; + final int NT = mCancelledThumbnails.size(); + if (NT > 0) { + thumbnails = new ArrayList<ActivityRecord>(mCancelledThumbnails); + mCancelledThumbnails.clear(); + } else { + thumbnails = null; + } + + if (isFrontStack(mHomeStack)) { + booting = mService.mBooting; + mService.mBooting = false; + } + + if (mStartingUsers.size() > 0) { + startingUsers = new ArrayList<UserStartedState>(mStartingUsers); + mStartingUsers.clear(); + } + + // Perform the following actions from unsynchronized state. + final IApplicationThread thumbnailThread = sendThumbnail; + mHandler.post(new Runnable() { + @Override + public void run() { + if (thumbnailThread != null) { + try { + thumbnailThread.requestThumbnail(token); + } catch (Exception e) { + Slog.w(TAG, "Exception thrown when requesting thumbnail", e); + mService.sendPendingThumbnail(null, token, null, null, true); + } + } + + // Report back to any thumbnail receivers. + for (int i = 0; i < NT; i++) { + ActivityRecord r = thumbnails.get(i); + mService.sendPendingThumbnail(r, null, null, null, true); + } + } + }); + + // Stop any activities that are scheduled to do so but have been + // waiting for the next one to start. + for (int i = 0; i < NS; i++) { + r = stops.get(i); + final ActivityStack stack = r.task.stack; + if (r.finishing) { + stack.finishCurrentActivityLocked(r, ActivityStack.FINISH_IMMEDIATELY, false); + } else { + stack.stopActivityLocked(r); + } + } + + // Finish any activities that are scheduled to do so but have been + // waiting for the next one to start. + for (int i = 0; i < NF; i++) { + r = finishes.get(i); + activityRemoved |= r.task.stack.destroyActivityLocked(r, true, false, "finish-idle"); + } + + if (booting) { + mService.finishBooting(); + } else if (startingUsers != null) { + for (int i = 0; i < startingUsers.size(); i++) { + mService.finishUserSwitch(startingUsers.get(i)); + } + } + + mService.trimApplications(); + //dump(); + //mWindowManager.dump(); + + if (enableScreen) { + mService.enableScreenAfterBoot(); + } + + if (activityRemoved) { + resumeTopActivitiesLocked(); + } + + return r; + } + + boolean handleAppDiedLocked(ProcessRecord app) { + boolean hasVisibleActivities = false; + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + hasVisibleActivities |= mStacks.get(stackNdx).handleAppDiedLocked(app); + } + return hasVisibleActivities; + } + + void closeSystemDialogsLocked() { + final int numStacks = mStacks.size(); + for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + stack.closeSystemDialogsLocked(); + } + } + + void removeUserLocked(int userId) { + mUserStackInFront.delete(userId); + } + + /** + * @return true if some activity was finished (or would have finished if doit were true). + */ + boolean forceStopPackageLocked(String name, boolean doit, boolean evenPersistent, int userId) { + boolean didSomething = false; + final int numStacks = mStacks.size(); + for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + if (stack.forceStopPackageLocked(name, doit, evenPersistent, userId)) { + didSomething = true; + } + } + return didSomething; + } + + void updatePreviousProcessLocked(ActivityRecord r) { + // Now that this process has stopped, we may want to consider + // it to be the previous app to try to keep around in case + // the user wants to return to it. + + // First, found out what is currently the foreground app, so that + // we don't blow away the previous app if this activity is being + // hosted by the process that is actually still the foreground. + ProcessRecord fgApp = null; + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + if (isFrontStack(stack)) { + if (stack.mResumedActivity != null) { + fgApp = stack.mResumedActivity.app; + } else if (stack.mPausingActivity != null) { + fgApp = stack.mPausingActivity.app; + } + break; + } + } + + // Now set this one as the previous process, only if that really + // makes sense to. + if (r.app != null && fgApp != null && r.app != fgApp + && r.lastVisibleTime > mService.mPreviousProcessVisibleTime + && r.app != mService.mHomeProcess) { + mService.mPreviousProcess = r.app; + mService.mPreviousProcessVisibleTime = r.lastVisibleTime; + } + } + + boolean resumeTopActivitiesLocked() { + return resumeTopActivitiesLocked(null, null, null); + } + + boolean resumeTopActivitiesLocked(ActivityStack targetStack, ActivityRecord target, + Bundle targetOptions) { + if (targetStack == null) { + targetStack = getFocusedStack(); + } + boolean result = false; + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + if (isFrontStack(stack)) { + if (stack == targetStack) { + result = stack.resumeTopActivityLocked(target, targetOptions); + } else { + stack.resumeTopActivityLocked(null); + } + } + } + return result; + } + + void finishTopRunningActivityLocked(ProcessRecord app) { + final int numStacks = mStacks.size(); + for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + stack.finishTopRunningActivityLocked(app); + } + } + + void findTaskToMoveToFrontLocked(int taskId, int flags, Bundle options) { + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + if (mStacks.get(stackNdx).findTaskToMoveToFrontLocked(taskId, flags, options)) { + if (DEBUG_STACK) Slog.d(TAG, "findTaskToMoveToFront: moved to front of stack=" + + mStacks.get(stackNdx)); + return; + } + } + } + + ActivityStack getStack(int stackId) { + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + if (stack.getStackId() == stackId) { + return stack; + } + } + return null; + } + + ArrayList<ActivityStack> getStacks() { + return new ArrayList<ActivityStack>(mStacks); + } + + int createStack() { + while (true) { + if (++mLastStackId <= HOME_STACK_ID) { + mLastStackId = HOME_STACK_ID + 1; + } + if (getStack(mLastStackId) == null) { + break; + } + } + mStacks.add(new ActivityStack(mService, mContext, mLooper, mLastStackId)); + return mLastStackId; + } + + void moveTaskToStack(int taskId, int stackId, boolean toTop) { + final TaskRecord task = anyTaskForIdLocked(taskId); + if (task == null) { + return; + } + final ActivityStack stack = getStack(stackId); + if (stack == null) { + Slog.w(TAG, "moveTaskToStack: no stack for id=" + stackId); + return; + } + removeTask(task); + stack.addTask(task, toTop); + mWindowManager.addTask(taskId, stackId, toTop); + resumeTopActivitiesLocked(); + } + + ActivityRecord findTaskLocked(ActivityRecord r) { + if (DEBUG_TASKS) Slog.d(TAG, "Looking for task of " + r); + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + if (!r.isApplicationActivity() && !stack.isHomeStack()) { + if (DEBUG_TASKS) Slog.d(TAG, "Skipping stack: " + stack); + continue; + } + final ActivityRecord ar = stack.findTaskLocked(r); + if (ar != null) { + return ar; + } + } + if (DEBUG_TASKS) Slog.d(TAG, "No task found"); + return null; + } + + ActivityRecord findActivityLocked(Intent intent, ActivityInfo info) { + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityRecord ar = mStacks.get(stackNdx).findActivityLocked(intent, info); + if (ar != null) { + return ar; + } + } + return null; + } + + void goingToSleepLocked() { + scheduleSleepTimeout(); + if (!mGoingToSleep.isHeld()) { + mGoingToSleep.acquire(); + if (mLaunchingActivity.isHeld()) { + if (VALIDATE_WAKE_LOCK_CALLER && Binder.getCallingUid() != Process.myUid()) { + throw new IllegalStateException("Calling must be system uid"); + } + mLaunchingActivity.release(); + mService.mHandler.removeMessages(LAUNCH_TIMEOUT_MSG); + } + } + checkReadyForSleepLocked(); + } + + boolean shutdownLocked(int timeout) { + boolean timedout = false; + goingToSleepLocked(); + + final long endTime = System.currentTimeMillis() + timeout; + while (true) { + boolean cantShutdown = false; + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + cantShutdown |= mStacks.get(stackNdx).checkReadyForSleepLocked(); + } + if (cantShutdown) { + long timeRemaining = endTime - System.currentTimeMillis(); + if (timeRemaining > 0) { + try { + mService.wait(timeRemaining); + } catch (InterruptedException e) { + } + } else { + Slog.w(TAG, "Activity manager shutdown timed out"); + timedout = true; + break; + } + } else { + break; + } + } + + // Force checkReadyForSleep to complete. + mSleepTimeout = true; + checkReadyForSleepLocked(); + + return timedout; + } + + void comeOutOfSleepIfNeededLocked() { + removeSleepTimeouts(); + if (mGoingToSleep.isHeld()) { + mGoingToSleep.release(); + } + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + stack.awakeFromSleepingLocked(); + if (isFrontStack(stack)) { + resumeTopActivitiesLocked(); + } + } + mGoingToSleepActivities.clear(); + } + + void activitySleptLocked(ActivityRecord r) { + mGoingToSleepActivities.remove(r); + checkReadyForSleepLocked(); + } + + void checkReadyForSleepLocked() { + if (!mService.isSleepingOrShuttingDown()) { + // Do not care. + return; + } + + if (!mSleepTimeout) { + boolean dontSleep = false; + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + dontSleep |= mStacks.get(stackNdx).checkReadyForSleepLocked(); + } + + if (mStoppingActivities.size() > 0) { + // Still need to tell some activities to stop; can't sleep yet. + if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to stop " + + mStoppingActivities.size() + " activities"); + scheduleIdleLocked(); + dontSleep = true; + } + + if (mGoingToSleepActivities.size() > 0) { + // Still need to tell some activities to sleep; can't sleep yet. + if (DEBUG_PAUSE) Slog.v(TAG, "Sleep still need to sleep " + + mGoingToSleepActivities.size() + " activities"); + dontSleep = true; + } + + if (dontSleep) { + return; + } + } + + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + mStacks.get(stackNdx).goToSleep(); + } + + removeSleepTimeouts(); + + if (mGoingToSleep.isHeld()) { + mGoingToSleep.release(); + } + if (mService.mShuttingDown) { + mService.notifyAll(); + } + } + + boolean reportResumedActivityLocked(ActivityRecord r) { + final ActivityStack stack = r.task.stack; + if (isFrontStack(stack)) { + mService.updateUsageStats(r, true); + } + if (allResumedActivitiesComplete()) { + ensureActivitiesVisibleLocked(null, 0); + mWindowManager.executeAppTransition(); + return true; + } + return false; + } + + void handleAppCrashLocked(ProcessRecord app) { + final int numStacks = mStacks.size(); + for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + stack.handleAppCrashLocked(app); + } + } + + void ensureActivitiesVisibleLocked(ActivityRecord starting, int configChanges) { + // First the front stacks. In case any are not fullscreen and are in front of home. + boolean showHomeBehindStack = false; + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + if (isFrontStack(stack)) { + showHomeBehindStack = + stack.ensureActivitiesVisibleLocked(starting, configChanges); + } + } + // Now do back stacks. + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + if (!isFrontStack(stack)) { + stack.ensureActivitiesVisibleLocked(starting, configChanges, showHomeBehindStack); + } + } + } + + void scheduleDestroyAllActivities(ProcessRecord app, String reason) { + final int numStacks = mStacks.size(); + for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + stack.scheduleDestroyActivities(app, false, reason); + } + } + + boolean switchUserLocked(int userId, UserStartedState uss) { + mUserStackInFront.put(mCurrentUser, getFocusedStack().getStackId()); + final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID); + mCurrentUser = userId; + + mStartingUsers.add(uss); + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + mStacks.get(stackNdx).switchUserLocked(userId); + } + + ActivityStack stack = getStack(restoreStackId); + if (stack == null) { + stack = mHomeStack; + } + final boolean homeInFront = stack.isHomeStack(); + moveHomeStack(homeInFront); + mWindowManager.moveTaskToTop(stack.topTask().taskId); + return homeInFront; + } + + final ArrayList<ActivityRecord> processStoppingActivitiesLocked(boolean remove) { + int N = mStoppingActivities.size(); + if (N <= 0) return null; + + ArrayList<ActivityRecord> stops = null; + + final boolean nowVisible = allResumedActivitiesVisible(); + for (int i=0; i<N; i++) { + ActivityRecord s = mStoppingActivities.get(i); + if (localLOGV) Slog.v(TAG, "Stopping " + s + ": nowVisible=" + + nowVisible + " waitingVisible=" + s.waitingVisible + + " finishing=" + s.finishing); + if (s.waitingVisible && nowVisible) { + mWaitingVisibleActivities.remove(s); + s.waitingVisible = false; + if (s.finishing) { + // If this activity is finishing, it is sitting on top of + // everyone else but we now know it is no longer needed... + // so get rid of it. Otherwise, we need to go through the + // normal flow and hide it once we determine that it is + // hidden by the activities in front of it. + if (localLOGV) Slog.v(TAG, "Before stopping, can hide: " + s); + mWindowManager.setAppVisibility(s.appToken, false); + } + } + if ((!s.waitingVisible || mService.isSleepingOrShuttingDown()) && remove) { + if (localLOGV) Slog.v(TAG, "Ready to stop: " + s); + if (stops == null) { + stops = new ArrayList<ActivityRecord>(); + } + stops.add(s); + mStoppingActivities.remove(i); + N--; + i--; + } + } + + return stops; + } + + void validateTopActivitiesLocked() { + for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + final ActivityRecord r = stack.topRunningActivityLocked(null); + final ActivityState state = r == null ? ActivityState.DESTROYED : r.state; + if (isFrontStack(stack)) { + if (r == null) { + Slog.e(TAG, "validateTop...: null top activity, stack=" + stack); + } else { + final ActivityRecord pausing = stack.mPausingActivity; + if (pausing != null && pausing == r) { + Slog.e(TAG, "validateTop...: top stack has pausing activity r=" + r + + " state=" + state); + } + if (state != ActivityState.INITIALIZING && state != ActivityState.RESUMED) { + Slog.e(TAG, "validateTop...: activity in front not resumed r=" + r + + " state=" + state); + } + } + } else { + final ActivityRecord resumed = stack.mResumedActivity; + if (resumed != null && resumed == r) { + Slog.e(TAG, "validateTop...: back stack has resumed activity r=" + r + + " state=" + state); + } + if (r != null && (state == ActivityState.INITIALIZING + || state == ActivityState.RESUMED)) { + Slog.e(TAG, "validateTop...: activity in back resumed r=" + r + + " state=" + state); + } + } + } + } + + private static String stackStateToString(int stackState) { + switch (stackState) { + case STACK_STATE_HOME_IN_FRONT: return "STACK_STATE_HOME_IN_FRONT"; + case STACK_STATE_HOME_TO_BACK: return "STACK_STATE_HOME_TO_BACK"; + case STACK_STATE_HOME_IN_BACK: return "STACK_STATE_HOME_IN_BACK"; + case STACK_STATE_HOME_TO_FRONT: return "STACK_STATE_HOME_TO_FRONT"; + default: return "Unknown stackState=" + stackState; + } + } + + public void dump(PrintWriter pw, String prefix) { + pw.print(prefix); pw.print("mDismissKeyguardOnNextActivity:"); + pw.println(mDismissKeyguardOnNextActivity); + pw.print(prefix); pw.print("mStackState="); pw.println(stackStateToString(mStackState)); + pw.print(prefix); pw.println("mSleepTimeout: " + mSleepTimeout); + pw.print(prefix); pw.println("mCurTaskId: " + mCurTaskId); + pw.print(prefix); pw.println("mUserStackInFront: " + mUserStackInFront); + } + + ArrayList<ActivityRecord> getDumpActivitiesLocked(String name) { + return getFocusedStack().getDumpActivitiesLocked(name); + } + + static boolean printThisActivity(PrintWriter pw, ActivityRecord activity, String dumpPackage, + boolean needSep, String prefix) { + if (activity != null) { + if (dumpPackage == null || dumpPackage.equals(activity.packageName)) { + if (needSep) { + pw.println(); + } + pw.print(prefix); + pw.println(activity); + return true; + } + } + return false; + } + + boolean dumpActivitiesLocked(FileDescriptor fd, PrintWriter pw, boolean dumpAll, + boolean dumpClient, String dumpPackage) { + boolean printed = false; + boolean needSep = false; + final int numStacks = mStacks.size(); + for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + final ActivityStack stack = mStacks.get(stackNdx); + StringBuilder stackHeader = new StringBuilder(128); + stackHeader.append(" Stack #"); + stackHeader.append(mStacks.indexOf(stack)); + stackHeader.append(":"); + printed |= stack.dumpActivitiesLocked(fd, pw, dumpAll, dumpClient, dumpPackage, needSep, + stackHeader.toString()); + printed |= dumpHistoryList(fd, pw, stack.mLRUActivities, " ", "Run", false, !dumpAll, + false, dumpPackage, true, " Running activities (most recent first):", null); + + needSep = printed; + boolean pr = printThisActivity(pw, stack.mPausingActivity, dumpPackage, needSep, + " mPausingActivity: "); + if (pr) { + printed = true; + needSep = false; + } + pr = printThisActivity(pw, stack.mResumedActivity, dumpPackage, needSep, + " mResumedActivity: "); + if (pr) { + printed = true; + needSep = false; + } + if (dumpAll) { + pr = printThisActivity(pw, stack.mLastPausedActivity, dumpPackage, needSep, + " mLastPausedActivity: "); + if (pr) { + printed = true; + needSep = true; + } + printed |= printThisActivity(pw, stack.mLastNoHistoryActivity, dumpPackage, + needSep, " mLastNoHistoryActivity: "); + } + needSep = printed; + } + + printed |= dumpHistoryList(fd, pw, mFinishingActivities, " ", "Fin", false, !dumpAll, + false, dumpPackage, true, " Activities waiting to finish:", null); + printed |= dumpHistoryList(fd, pw, mStoppingActivities, " ", "Stop", false, !dumpAll, + false, dumpPackage, true, " Activities waiting to stop:", null); + printed |= dumpHistoryList(fd, pw, mWaitingVisibleActivities, " ", "Wait", false, !dumpAll, + false, dumpPackage, true, " Activities waiting for another to become visible:", + null); + printed |= dumpHistoryList(fd, pw, mGoingToSleepActivities, " ", "Sleep", false, !dumpAll, + false, dumpPackage, true, " Activities waiting to sleep:", null); + printed |= dumpHistoryList(fd, pw, mGoingToSleepActivities, " ", "Sleep", false, !dumpAll, + false, dumpPackage, true, " Activities waiting to sleep:", null); + + return printed; + } + + static boolean dumpHistoryList(FileDescriptor fd, PrintWriter pw, List<ActivityRecord> list, + String prefix, String label, boolean complete, boolean brief, boolean client, + String dumpPackage, boolean needNL, String header1, String header2) { + TaskRecord lastTask = null; + String innerPrefix = null; + String[] args = null; + boolean printed = false; + for (int i=list.size()-1; i>=0; i--) { + final ActivityRecord r = list.get(i); + if (dumpPackage != null && !dumpPackage.equals(r.packageName)) { + continue; + } + if (innerPrefix == null) { + innerPrefix = prefix + " "; + args = new String[0]; + } + printed = true; + final boolean full = !brief && (complete || !r.isInHistory()); + if (needNL) { + pw.println(""); + needNL = false; + } + if (header1 != null) { + pw.println(header1); + header1 = null; + } + if (header2 != null) { + pw.println(header2); + header2 = null; + } + if (lastTask != r.task) { + lastTask = r.task; + pw.print(prefix); + pw.print(full ? "* " : " "); + pw.println(lastTask); + if (full) { + lastTask.dump(pw, prefix + " "); + } else if (complete) { + // Complete + brief == give a summary. Isn't that obvious?!? + if (lastTask.intent != null) { + pw.print(prefix); pw.print(" "); + pw.println(lastTask.intent.toInsecureStringWithClip()); + } + } + } + pw.print(prefix); pw.print(full ? " * " : " "); pw.print(label); + pw.print(" #"); pw.print(i); pw.print(": "); + pw.println(r); + if (full) { + r.dump(pw, innerPrefix); + } else if (complete) { + // Complete + brief == give a summary. Isn't that obvious?!? + pw.print(innerPrefix); pw.println(r.intent.toInsecureString()); + if (r.app != null) { + pw.print(innerPrefix); pw.println(r.app); + } + } + if (client && r.app != null && r.app.thread != null) { + // flush anything that is already in the PrintWriter since the thread is going + // to write to the file descriptor directly + pw.flush(); + try { + TransferPipe tp = new TransferPipe(); + try { + r.app.thread.dumpActivity(tp.getWriteFd().getFileDescriptor(), + r.appToken, innerPrefix, args); + // Short timeout, since blocking here can + // deadlock with the application. + tp.go(fd, 2000); + } finally { + tp.kill(); + } + } catch (IOException e) { + pw.println(innerPrefix + "Failure while dumping the activity: " + e); + } catch (RemoteException e) { + pw.println(innerPrefix + "Got a RemoteException while dumping the activity"); + } + needNL = true; + } + } + return printed; + } + + void scheduleIdleTimeoutLocked(ActivityRecord next) { + if (DEBUG_IDLE) Slog.d(TAG, "scheduleIdleTimeoutLocked: Callers=" + Debug.getCallers(4)); + Message msg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG, next); + mHandler.sendMessageDelayed(msg, IDLE_TIMEOUT); + } + + final void scheduleIdleLocked() { + mHandler.sendEmptyMessage(IDLE_NOW_MSG); + } + + void removeTimeoutsForActivityLocked(ActivityRecord r) { + if (DEBUG_IDLE) Slog.d(TAG, "removeTimeoutsForActivity: Callers=" + Debug.getCallers(4)); + mHandler.removeMessages(IDLE_TIMEOUT_MSG, r); + } + + final void scheduleResumeTopActivities() { + mHandler.sendEmptyMessage(RESUME_TOP_ACTIVITY_MSG); + } + + void removeSleepTimeouts() { + mSleepTimeout = false; + mHandler.removeMessages(SLEEP_TIMEOUT_MSG); + } + + final void scheduleSleepTimeout() { + removeSleepTimeouts(); + mHandler.sendEmptyMessageDelayed(SLEEP_TIMEOUT_MSG, SLEEP_TIMEOUT); + } + + private final class ActivityStackSupervisorHandler extends Handler { + + public ActivityStackSupervisorHandler(Looper looper) { + super(looper); + } + + void activityIdleInternal(ActivityRecord r) { + synchronized (mService) { + activityIdleInternalLocked(r != null ? r.appToken : null, true, null); + } + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case IDLE_TIMEOUT_MSG: { + if (DEBUG_IDLE) Slog.d(TAG, "handleMessage: IDLE_TIMEOUT_MSG: r=" + msg.obj); + if (mService.mDidDexOpt) { + mService.mDidDexOpt = false; + Message nmsg = mHandler.obtainMessage(IDLE_TIMEOUT_MSG); + nmsg.obj = msg.obj; + mHandler.sendMessageDelayed(nmsg, IDLE_TIMEOUT); + return; + } + // We don't at this point know if the activity is fullscreen, + // so we need to be conservative and assume it isn't. + activityIdleInternal((ActivityRecord)msg.obj); + } break; + case IDLE_NOW_MSG: { + if (DEBUG_IDLE) Slog.d(TAG, "handleMessage: IDLE_NOW_MSG: r=" + msg.obj); + activityIdleInternal((ActivityRecord)msg.obj); + } break; + case RESUME_TOP_ACTIVITY_MSG: { + synchronized (mService) { + resumeTopActivitiesLocked(); + } + } break; + case SLEEP_TIMEOUT_MSG: { + synchronized (mService) { + if (mService.isSleepingOrShuttingDown()) { + Slog.w(TAG, "Sleep timeout! Sleeping now."); + mSleepTimeout = true; + checkReadyForSleepLocked(); + } + } + } break; + case LAUNCH_TIMEOUT_MSG: { + if (mService.mDidDexOpt) { + mService.mDidDexOpt = false; + mHandler.sendEmptyMessageDelayed(LAUNCH_TIMEOUT_MSG, LAUNCH_TIMEOUT); + return; + } + synchronized (mService) { + if (mLaunchingActivity.isHeld()) { + Slog.w(TAG, "Launch timeout has expired, giving up wake lock!"); + if (VALIDATE_WAKE_LOCK_CALLER + && Binder.getCallingUid() != Process.myUid()) { + throw new IllegalStateException("Calling must be system uid"); + } + mLaunchingActivity.release(); + } + } + } break; + } + } + } +} diff --git a/services/java/com/android/server/am/AppBindRecord.java b/services/java/com/android/server/am/AppBindRecord.java index f1c54fa61411..06265fd6856d 100644 --- a/services/java/com/android/server/am/AppBindRecord.java +++ b/services/java/com/android/server/am/AppBindRecord.java @@ -23,7 +23,7 @@ import java.util.Iterator; /** * An association between a service and one of its client applications. */ -class AppBindRecord { +final class AppBindRecord { final ServiceRecord service; // The running service. final IntentBindRecord intent; // The intent we are bound to. final ProcessRecord client; // Who has started/bound the service. diff --git a/services/java/com/android/server/am/AppErrorDialog.java b/services/java/com/android/server/am/AppErrorDialog.java index ffa1e92061d9..0ba62c53dcd9 100644 --- a/services/java/com/android/server/am/AppErrorDialog.java +++ b/services/java/com/android/server/am/AppErrorDialog.java @@ -16,8 +16,6 @@ package com.android.server.am; -import static android.view.WindowManager.LayoutParams.FLAG_SYSTEM_ERROR; - import android.content.Context; import android.content.DialogInterface; import android.content.res.Resources; @@ -25,7 +23,7 @@ import android.os.Handler; import android.os.Message; import android.view.WindowManager; -class AppErrorDialog extends BaseErrorDialog { +final class AppErrorDialog extends BaseErrorDialog { private final ActivityManagerService mService; private final AppErrorResult mResult; private final ProcessRecord mProc; @@ -72,10 +70,10 @@ class AppErrorDialog extends BaseErrorDialog { } setTitle(res.getText(com.android.internal.R.string.aerr_title)); - getWindow().addFlags(FLAG_SYSTEM_ERROR); WindowManager.LayoutParams attrs = getWindow().getAttributes(); attrs.setTitle("Application Error: " + app.info.processName); - attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; + attrs.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR + | WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; getWindow().setAttributes(attrs); if (app.persistent) { getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); diff --git a/services/java/com/android/server/am/AppErrorResult.java b/services/java/com/android/server/am/AppErrorResult.java index ebfcfe2ccb36..c6a5720b537e 100644 --- a/services/java/com/android/server/am/AppErrorResult.java +++ b/services/java/com/android/server/am/AppErrorResult.java @@ -16,8 +16,7 @@ package com.android.server.am; - -class AppErrorResult { +final class AppErrorResult { public void set(int res) { synchronized (this) { mHasResult = true; diff --git a/services/java/com/android/server/am/AppNotRespondingDialog.java b/services/java/com/android/server/am/AppNotRespondingDialog.java index af61c9b7b71d..f4c166421c35 100644 --- a/services/java/com/android/server/am/AppNotRespondingDialog.java +++ b/services/java/com/android/server/am/AppNotRespondingDialog.java @@ -16,8 +16,6 @@ package com.android.server.am; -import static android.view.WindowManager.LayoutParams.FLAG_SYSTEM_ERROR; - import android.content.ActivityNotFoundException; import android.content.Context; import android.content.DialogInterface; @@ -28,7 +26,7 @@ import android.os.Message; import android.util.Slog; import android.view.WindowManager; -class AppNotRespondingDialog extends BaseErrorDialog { +final class AppNotRespondingDialog extends BaseErrorDialog { private static final String TAG = "AppNotRespondingDialog"; // Event 'what' codes @@ -94,10 +92,10 @@ class AppNotRespondingDialog extends BaseErrorDialog { if (aboveSystem) { getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ERROR); } - getWindow().addFlags(FLAG_SYSTEM_ERROR); WindowManager.LayoutParams attrs = getWindow().getAttributes(); attrs.setTitle("Application Not Responding: " + app.info.processName); - attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; + attrs.privateFlags = WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR | + WindowManager.LayoutParams.PRIVATE_FLAG_SHOW_FOR_ALL_USERS; getWindow().setAttributes(attrs); } @@ -128,6 +126,7 @@ class AppNotRespondingDialog extends BaseErrorDialog { if (app.anrDialog == AppNotRespondingDialog.this) { app.anrDialog = null; } + mService.mServices.scheduleServiceTimeoutLocked(app); } break; } diff --git a/services/java/com/android/server/am/AppWaitingForDebuggerDialog.java b/services/java/com/android/server/am/AppWaitingForDebuggerDialog.java index d08bb101c93f..27865a88da99 100644 --- a/services/java/com/android/server/am/AppWaitingForDebuggerDialog.java +++ b/services/java/com/android/server/am/AppWaitingForDebuggerDialog.java @@ -22,7 +22,7 @@ import android.os.Handler; import android.os.Message; import android.view.WindowManager; -class AppWaitingForDebuggerDialog extends BaseErrorDialog { +final class AppWaitingForDebuggerDialog extends BaseErrorDialog { final ActivityManagerService mService; final ProcessRecord mProc; private CharSequence mAppName; diff --git a/services/java/com/android/server/am/BackupRecord.java b/services/java/com/android/server/am/BackupRecord.java index 7e7310649572..5fa7e6a37133 100644 --- a/services/java/com/android/server/am/BackupRecord.java +++ b/services/java/com/android/server/am/BackupRecord.java @@ -21,7 +21,7 @@ import com.android.internal.os.BatteryStatsImpl; import android.content.pm.ApplicationInfo; /** @hide */ -class BackupRecord { +final class BackupRecord { // backup/restore modes public static final int BACKUP_NORMAL = 0; public static final int BACKUP_FULL = 1; diff --git a/services/java/com/android/server/am/BatteryStatsService.java b/services/java/com/android/server/am/BatteryStatsService.java index d19c7f6c7620..0dd950e1c566 100644 --- a/services/java/com/android/server/am/BatteryStatsService.java +++ b/services/java/com/android/server/am/BatteryStatsService.java @@ -22,11 +22,13 @@ import android.bluetooth.BluetoothProfile; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; +import android.os.BatteryStats; import android.os.Binder; import android.os.IBinder; import android.os.Parcel; import android.os.Process; import android.os.ServiceManager; +import android.os.UserHandle; import android.os.WorkSource; import android.telephony.SignalStrength; import android.telephony.TelephonyManager; @@ -58,7 +60,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub { public void publish(Context context) { mContext = context; - ServiceManager.addService("batteryinfo", asBinder()); + ServiceManager.addService(BatteryStats.SERVICE_NAME, asBinder()); mStats.setNumSpeedSteps(new PowerProfile(mContext).getNumSpeedSteps()); mStats.setRadioScanningTimeout(mContext.getResources().getInteger( com.android.internal.R.integer.config_radioScanningTimeout) @@ -76,7 +78,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub { if (sService != null) { return sService; } - IBinder b = ServiceManager.getService("batteryinfo"); + IBinder b = ServiceManager.getService(BatteryStats.SERVICE_NAME); sService = asInterface(b); return sService; } @@ -431,6 +433,7 @@ public final class BatteryStatsService extends IBatteryStats.Stub { } } + @Override public void noteNetworkInterfaceType(String iface, int type) { enforceCallingPermission(); synchronized (mStats) { @@ -438,6 +441,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub { } } + @Override + public void noteNetworkStatsEnabled() { + enforceCallingPermission(); + synchronized (mStats) { + mStats.noteNetworkStatsEnabledLocked(); + } + } + public boolean isOnBattery() { return mStats.isOnBattery(); } @@ -469,12 +480,14 @@ public final class BatteryStatsService extends IBatteryStats.Stub { } private void dumpHelp(PrintWriter pw) { - pw.println("Battery stats (batteryinfo) dump options:"); - pw.println(" [--checkin] [--reset] [--write] [-h]"); + pw.println("Battery stats (batterystats) dump options:"); + pw.println(" [--checkin] [-c] [--unplugged] [--reset] [--write] [-h] [<package.name>]"); pw.println(" --checkin: format output for a checkin report."); + pw.println(" --unplugged: only output data since last unplugged."); pw.println(" --reset: reset the stats, clearing all current data."); pw.println(" --write: force write current collected stats to disk."); pw.println(" -h: print this help text."); + pw.println(" <package.name>: optional name of package to filter output by."); } @Override @@ -488,11 +501,19 @@ public final class BatteryStatsService extends IBatteryStats.Stub { } boolean isCheckin = false; + boolean includeHistory = false; + boolean isUnpluggedOnly = false; boolean noOutput = false; + int reqUid = -1; if (args != null) { for (String arg : args) { if ("--checkin".equals(arg)) { isCheckin = true; + } else if ("-c".equals(arg)) { + isCheckin = true; + includeHistory = true; + } else if ("--unplugged".equals(arg)) { + isUnpluggedOnly = true; } else if ("--reset".equals(arg)) { synchronized (mStats) { mStats.resetAllStatsLocked(); @@ -510,9 +531,20 @@ public final class BatteryStatsService extends IBatteryStats.Stub { return; } else if ("-a".equals(arg)) { // fall through - } else { + } else if (arg.length() > 0 && arg.charAt(0) == '-'){ pw.println("Unknown option: " + arg); dumpHelp(pw); + return; + } else { + // Not an option, last argument must be a package name. + try { + reqUid = mContext.getPackageManager().getPackageUid(arg, + UserHandle.getCallingUserId()); + } catch (PackageManager.NameNotFoundException e) { + pw.println("Unknown package: " + arg); + dumpHelp(pw); + return; + } } } } @@ -522,11 +554,11 @@ public final class BatteryStatsService extends IBatteryStats.Stub { if (isCheckin) { List<ApplicationInfo> apps = mContext.getPackageManager().getInstalledApplications(0); synchronized (mStats) { - mStats.dumpCheckinLocked(pw, args, apps); + mStats.dumpCheckinLocked(pw, apps, isUnpluggedOnly, includeHistory); } } else { synchronized (mStats) { - mStats.dumpLocked(pw); + mStats.dumpLocked(pw, isUnpluggedOnly, reqUid); } } } diff --git a/services/java/com/android/server/am/BroadcastFilter.java b/services/java/com/android/server/am/BroadcastFilter.java index c631b6e0f9d9..986b8ea3c41f 100644 --- a/services/java/com/android/server/am/BroadcastFilter.java +++ b/services/java/com/android/server/am/BroadcastFilter.java @@ -22,7 +22,7 @@ import android.util.Printer; import java.io.PrintWriter; -class BroadcastFilter extends IntentFilter { +final class BroadcastFilter extends IntentFilter { // Back-pointer to the list this filter is in. final ReceiverList receiverList; final String packageName; diff --git a/services/java/com/android/server/am/BroadcastQueue.java b/services/java/com/android/server/am/BroadcastQueue.java index ac7eb8954a40..1d6970fd4a8c 100644 --- a/services/java/com/android/server/am/BroadcastQueue.java +++ b/services/java/com/android/server/am/BroadcastQueue.java @@ -47,15 +47,16 @@ import android.util.Slog; * We keep two broadcast queues and associated bookkeeping, one for those at * foreground priority, and one for normal (background-priority) broadcasts. */ -public class BroadcastQueue { +public final class BroadcastQueue { static final String TAG = "BroadcastQueue"; static final String TAG_MU = ActivityManagerService.TAG_MU; static final boolean DEBUG_BROADCAST = ActivityManagerService.DEBUG_BROADCAST; static final boolean DEBUG_BROADCAST_LIGHT = ActivityManagerService.DEBUG_BROADCAST_LIGHT; static final boolean DEBUG_MU = ActivityManagerService.DEBUG_MU; - static final int MAX_BROADCAST_HISTORY = 25; - static final int MAX_BROADCAST_SUMMARY_HISTORY = 100; + static final int MAX_BROADCAST_HISTORY = ActivityManager.isLowRamDeviceStatic() ? 10 : 25; + static final int MAX_BROADCAST_SUMMARY_HISTORY + = ActivityManager.isLowRamDeviceStatic() ? 25 : 100; final ActivityManagerService mService; @@ -70,14 +71,20 @@ public class BroadcastQueue { final long mTimeoutPeriod; /** + * If true, we can delay broadcasts while waiting services to finish in the previous + * receiver's process. + */ + final boolean mDelayBehindServices; + + /** * Lists of all active broadcasts that are to be executed immediately * (without waiting for another broadcast to finish). Currently this only * contains broadcasts to registered receivers, to avoid spinning up * a bunch of processes to execute IntentReceiver components. Background- * and foreground-priority broadcasts are queued separately. */ - final ArrayList<BroadcastRecord> mParallelBroadcasts - = new ArrayList<BroadcastRecord>(); + final ArrayList<BroadcastRecord> mParallelBroadcasts = new ArrayList<BroadcastRecord>(); + /** * List of all active broadcasts that are to be executed one at a time. * The object at the top of the list is the currently activity broadcasts; @@ -85,20 +92,17 @@ public class BroadcastQueue { * broadcasts, separate background- and foreground-priority queues are * maintained. */ - final ArrayList<BroadcastRecord> mOrderedBroadcasts - = new ArrayList<BroadcastRecord>(); + final ArrayList<BroadcastRecord> mOrderedBroadcasts = new ArrayList<BroadcastRecord>(); /** * Historical data of past broadcasts, for debugging. */ - final BroadcastRecord[] mBroadcastHistory - = new BroadcastRecord[MAX_BROADCAST_HISTORY]; + final BroadcastRecord[] mBroadcastHistory = new BroadcastRecord[MAX_BROADCAST_HISTORY]; /** * Summary of historical data of past broadcasts, for debugging. */ - final Intent[] mBroadcastSummaryHistory - = new Intent[MAX_BROADCAST_SUMMARY_HISTORY]; + final Intent[] mBroadcastSummaryHistory = new Intent[MAX_BROADCAST_SUMMARY_HISTORY]; /** * Set when we current have a BROADCAST_INTENT_MSG in flight. @@ -128,10 +132,6 @@ public class BroadcastQueue { static final int BROADCAST_TIMEOUT_MSG = ActivityManagerService.FIRST_BROADCAST_QUEUE_MSG + 1; final Handler mHandler = new Handler() { - //public Handler() { - // if (localLOGV) Slog.v(TAG, "Handler started!"); - //} - public void handleMessage(Message msg) { switch (msg.what) { case BROADCAST_INTENT_MSG: { @@ -163,10 +163,12 @@ public class BroadcastQueue { } } - BroadcastQueue(ActivityManagerService service, String name, long timeoutPeriod) { + BroadcastQueue(ActivityManagerService service, String name, long timeoutPeriod, + boolean allowDelayBehindServices) { mService = service; mQueueName = name; mTimeoutPeriod = timeoutPeriod; + mDelayBehindServices = allowDelayBehindServices; } public boolean isPendingBroadcastProcessLocked(int pid) { @@ -217,7 +219,8 @@ public class BroadcastQueue { r.receiver = app.thread.asBinder(); r.curApp = app; app.curReceiver = r; - mService.updateLruProcessLocked(app, true); + app.forceProcessStateUpTo(ActivityManager.PROCESS_STATE_RECEIVER); + mService.updateLruProcessLocked(app, true, false); // Tell the application to launch this receiver. r.intent.setComponent(r.curComponent); @@ -230,7 +233,8 @@ public class BroadcastQueue { mService.ensurePackageDexOpt(r.intent.getComponent().getPackageName()); app.thread.scheduleReceiver(new Intent(r.intent), r.curReceiver, mService.compatibilityInfoForPackageLocked(r.curReceiver.applicationInfo), - r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId); + r.resultCode, r.resultData, r.resultExtras, r.ordered, r.userId, + app.repProcState); if (DEBUG_BROADCAST) Slog.v(TAG, "Process cur broadcast " + r + " DELIVERED for app " + app); started = true; @@ -258,7 +262,7 @@ public class BroadcastQueue { + br.curComponent.flattenToShortString(), e); logBroadcastReceiverDiscardLocked(br); finishReceiverLocked(br, br.resultCode, br.resultData, - br.resultExtras, br.resultAbort, true); + br.resultExtras, br.resultAbort, false); scheduleBroadcastsLocked(); // We need to reset the state if we failed to start the receiver. br.state = BroadcastRecord.IDLE; @@ -287,7 +291,7 @@ public class BroadcastQueue { // let the broadcast continue. logBroadcastReceiverDiscardLocked(r); finishReceiverLocked(r, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); + r.resultExtras, r.resultAbort, false); reschedule = true; } @@ -297,7 +301,7 @@ public class BroadcastQueue { "[" + mQueueName + "] skip & discard pending app " + r); logBroadcastReceiverDiscardLocked(r); finishReceiverLocked(r, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); + r.resultExtras, r.resultAbort, false); reschedule = true; } if (reschedule) { @@ -328,14 +332,12 @@ public class BroadcastQueue { } public boolean finishReceiverLocked(BroadcastRecord r, int resultCode, - String resultData, Bundle resultExtras, boolean resultAbort, - boolean explicit) { - int state = r.state; + String resultData, Bundle resultExtras, boolean resultAbort, boolean waitForServices) { + final int state = r.state; + final ActivityInfo receiver = r.curReceiver; r.state = BroadcastRecord.IDLE; if (state == BroadcastRecord.IDLE) { - if (explicit) { - Slog.w(TAG, "finishReceiver [" + mQueueName + "] called but state is IDLE"); - } + Slog.w(TAG, "finishReceiver [" + mQueueName + "] called but state is IDLE"); } r.receiver = null; r.intent.setComponent(null); @@ -346,15 +348,47 @@ public class BroadcastQueue { r.curFilter.receiverList.curBroadcast = null; } r.curFilter = null; - r.curApp = null; - r.curComponent = null; r.curReceiver = null; + r.curApp = null; mPendingBroadcast = null; r.resultCode = resultCode; r.resultData = resultData; r.resultExtras = resultExtras; - r.resultAbort = resultAbort; + if (resultAbort && (r.intent.getFlags()&Intent.FLAG_RECEIVER_NO_ABORT) == 0) { + r.resultAbort = resultAbort; + } else { + r.resultAbort = false; + } + + if (waitForServices && r.curComponent != null && r.queue.mDelayBehindServices + && r.queue.mOrderedBroadcasts.size() > 0 + && r.queue.mOrderedBroadcasts.get(0) == r) { + ActivityInfo nextReceiver; + if (r.nextReceiver < r.receivers.size()) { + Object obj = r.receivers.get(r.nextReceiver); + nextReceiver = (obj instanceof ActivityInfo) ? (ActivityInfo)obj : null; + } else { + nextReceiver = null; + } + // Don't do this if the next receive is in the same process as the current one. + if (receiver == null || nextReceiver == null + || receiver.applicationInfo.uid != nextReceiver.applicationInfo.uid + || !receiver.processName.equals(nextReceiver.processName)) { + // In this case, we are ready to process the next receiver for the current broadcast, + //Â but are on a queue that would like to wait for services to finish before moving + // on. If there are background services currently starting, then we will go into a + // special state where we hold off on continuing this broadcast until they are done. + if (mService.mServices.hasBackgroundServices(r.userId)) { + Slog.i(ActivityManagerService.TAG, "Delay finish: " + + r.curComponent.flattenToShortString()); + r.state = BroadcastRecord.WAITING_SERVICES; + return false; + } + } + } + + r.curComponent = null; // We will process the next receiver right now if this is finishing // an app receiver (which is always asynchronous) or after we have @@ -363,6 +397,18 @@ public class BroadcastQueue { || state == BroadcastRecord.CALL_DONE_RECEIVE; } + public void backgroundServicesFinishedLocked(int userId) { + if (mOrderedBroadcasts.size() > 0) { + BroadcastRecord br = mOrderedBroadcasts.get(0); + if (br.userId == userId && br.state == BroadcastRecord.WAITING_SERVICES) { + Slog.i(ActivityManagerService.TAG, "Resuming delayed broadcast"); + br.curComponent = null; + br.state = BroadcastRecord.IDLE; + processNextBroadcast(false); + } + } + } + private static void performReceiveLocked(ProcessRecord app, IIntentReceiver receiver, Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) throws RemoteException { @@ -371,7 +417,7 @@ public class BroadcastQueue { // 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, sticky, sendingUser); + data, extras, ordered, sticky, sendingUser, app.repProcState); } else { receiver.performReceive(intent, resultCode, data, extras, ordered, sticky, sendingUser); @@ -410,7 +456,7 @@ public class BroadcastQueue { } } if (r.appOp != AppOpsManager.OP_NONE) { - int mode = mService.mAppOpsService.checkOperation(r.appOp, + int mode = mService.mAppOpsService.noteOperation(r.appOp, filter.receiverList.uid, filter.packageName); if (mode != AppOpsManager.MODE_ALLOWED) { if (DEBUG_BROADCAST) Slog.v(TAG, @@ -419,6 +465,16 @@ public class BroadcastQueue { skip = true; } } + if (!skip) { + skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid, + r.callingPid, r.resolvedType, filter.receiverList.uid); + } + + if (filter.receiverList.app == null || filter.receiverList.app.crashing) { + Slog.w(TAG, "Skipping deliver [" + mQueueName + "] " + r + + " to " + filter.receiverList + ": process crashing"); + skip = true; + } if (!skip) { // If this is not being sent as an ordered broadcast, then we @@ -437,7 +493,7 @@ public class BroadcastQueue { // are already core system stuff so don't matter for this. r.curApp = filter.receiverList.app; filter.receiverList.app.curReceiver = r; - mService.updateOomAdjLocked(); + mService.updateOomAdjLocked(r.curApp, true); } } try { @@ -515,8 +571,8 @@ public class BroadcastQueue { boolean isDead; synchronized (mService.mPidsSelfLocked) { - isDead = (mService.mPidsSelfLocked.get( - mPendingBroadcast.curApp.pid) == null); + ProcessRecord proc = mService.mPidsSelfLocked.get(mPendingBroadcast.curApp.pid); + isDead = proc == null || proc.crashing; } if (!isDead) { // It's still alive, so keep waiting @@ -600,7 +656,7 @@ public class BroadcastQueue { new Intent(r.intent), r.resultCode, r.resultData, r.resultExtras, false, false, r.userId); // Set this to null so that the reference - // (local and remote) isnt kept in the mBroadcastHistory. + // (local and remote) isn't kept in the mBroadcastHistory. r.resultTo = null; } catch (RemoteException e) { Slog.w(TAG, "Failure [" @@ -717,7 +773,7 @@ public class BroadcastQueue { } } if (r.appOp != AppOpsManager.OP_NONE) { - int mode = mService.mAppOpsService.checkOperation(r.appOp, + int mode = mService.mAppOpsService.noteOperation(r.appOp, info.activityInfo.applicationInfo.uid, info.activityInfo.packageName); if (mode != AppOpsManager.MODE_ALLOWED) { if (DEBUG_BROADCAST) Slog.v(TAG, @@ -727,6 +783,10 @@ public class BroadcastQueue { skip = true; } } + if (!skip) { + skip = !mService.mIntentFirewall.checkBroadcast(r.intent, r.callingUid, + r.callingPid, r.resolvedType, info.activityInfo.applicationInfo.uid); + } boolean isSingleton = false; try { isSingleton = mService.isSingleton(info.activityInfo.processName, @@ -749,10 +809,8 @@ public class BroadcastQueue { } if (r.curApp != null && r.curApp.crashing) { // If the target process is crashing, just skip it. - if (DEBUG_BROADCAST) Slog.v(TAG, - "Skipping deliver ordered [" - + mQueueName + "] " + r + " to " + r.curApp - + ": process crashing"); + Slog.w(TAG, "Skipping deliver ordered [" + mQueueName + "] " + r + + " to " + r.curApp + ": process crashing"); skip = true; } @@ -792,10 +850,10 @@ public class BroadcastQueue { // Is this receiver's application already running? ProcessRecord app = mService.getProcessRecordLocked(targetProcess, - info.activityInfo.applicationInfo.uid); + info.activityInfo.applicationInfo.uid, false); if (app != null && app.thread != null) { try { - app.addPackage(info.activityInfo.packageName); + app.addPackage(info.activityInfo.packageName, mService.mProcessStats); processCurBroadcastLocked(r, app); return; } catch (RemoteException e) { @@ -811,7 +869,7 @@ public class BroadcastQueue { // sent the broadcast. logBroadcastReceiverDiscardLocked(r); finishReceiverLocked(r, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); + r.resultExtras, r.resultAbort, false); scheduleBroadcastsLocked(); // We need to reset the state if we failed to start the receiver. r.state = BroadcastRecord.IDLE; @@ -830,7 +888,7 @@ public class BroadcastQueue { info.activityInfo.applicationInfo, true, r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, "broadcast", r.curComponent, - (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false)) + (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false)) == null) { // Ah, this recipient is unavailable. Finish it if necessary, // and mark the broadcast record as ready for the next. @@ -840,7 +898,7 @@ public class BroadcastQueue { + r.intent + ": process is bad"); logBroadcastReceiverDiscardLocked(r); finishReceiverLocked(r, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); + r.resultExtras, r.resultAbort, false); scheduleBroadcastsLocked(); r.state = BroadcastRecord.IDLE; return; @@ -907,7 +965,21 @@ public class BroadcastQueue { } } - Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r.receiver + BroadcastRecord br = mOrderedBroadcasts.get(0); + if (br.state == BroadcastRecord.WAITING_SERVICES) { + // In this case the broadcast had already finished, but we had decided to wait + // for started services to finish as well before going on. So if we have actually + // waited long enough time timeout the broadcast, let's give up on the whole thing + // and just move on to the next. + Slog.i(ActivityManagerService.TAG, "Waited long enough for: " + (br.curComponent != null + ? br.curComponent.flattenToShortString() : "(null)")); + br.curComponent = null; + br.state = BroadcastRecord.IDLE; + processNextBroadcast(false); + return; + } + + Slog.w(TAG, "Timeout of broadcast " + r + " - receiver=" + r. receiver + ", started " + (now - r.receiverTime) + "ms ago"); r.receiverTime = now; r.anrCount++; @@ -947,7 +1019,7 @@ public class BroadcastQueue { // Move on to the next receiver. finishReceiverLocked(r, r.resultCode, r.resultData, - r.resultExtras, r.resultAbort, true); + r.resultExtras, r.resultAbort, false); scheduleBroadcastsLocked(); if (anrMessage != null) { diff --git a/services/java/com/android/server/am/BroadcastRecord.java b/services/java/com/android/server/am/BroadcastRecord.java index 83cc0ea5a694..b2cfd7a87242 100644 --- a/services/java/com/android/server/am/BroadcastRecord.java +++ b/services/java/com/android/server/am/BroadcastRecord.java @@ -36,7 +36,7 @@ import java.util.List; /** * An active intent broadcast. */ -class BroadcastRecord extends Binder { +final class BroadcastRecord extends Binder { final Intent intent; // the original intent that generated us final ComponentName targetComp; // original component name set on the intent final ProcessRecord callerApp; // process that sent this @@ -47,6 +47,7 @@ class BroadcastRecord extends Binder { final boolean sticky; // originated from existing sticky data? final boolean initialSticky; // initial broadcast from register to sticky? final int userId; // user id this broadcast was for + final String resolvedType; // the resolved data type final String requiredPermission; // a permission the caller has required final int appOp; // an app op that is associated with this broadcast final List receivers; // contains BroadcastFilter and ResolveInfo @@ -69,6 +70,7 @@ class BroadcastRecord extends Binder { static final int APP_RECEIVE = 1; static final int CALL_IN_RECEIVE = 2; static final int CALL_DONE_RECEIVE = 3; + static final int WAITING_SERVICES = 4; // The following are set when we are calling a receiver (one that // was found in our list of registered receivers). @@ -152,6 +154,7 @@ class BroadcastRecord extends Binder { case APP_RECEIVE: stateStr=" (APP_RECEIVE)"; break; case CALL_IN_RECEIVE: stateStr=" (CALL_IN_RECEIVE)"; break; case CALL_DONE_RECEIVE: stateStr=" (CALL_DONE_RECEIVE)"; break; + case WAITING_SERVICES: stateStr=" (WAITING_SERVICES)"; break; } pw.print(prefix); pw.print("state="); pw.print(state); pw.println(stateStr); } @@ -171,8 +174,8 @@ class BroadcastRecord extends Binder { BroadcastRecord(BroadcastQueue _queue, Intent _intent, ProcessRecord _callerApp, String _callerPackage, - int _callingPid, int _callingUid, String _requiredPermission, int _appOp, - List _receivers, IIntentReceiver _resultTo, int _resultCode, + int _callingPid, int _callingUid, String _resolvedType, String _requiredPermission, + int _appOp, List _receivers, IIntentReceiver _resultTo, int _resultCode, String _resultData, Bundle _resultExtras, boolean _serialized, boolean _sticky, boolean _initialSticky, int _userId) { @@ -183,6 +186,7 @@ class BroadcastRecord extends Binder { callerPackage = _callerPackage; callingPid = _callingPid; callingUid = _callingUid; + resolvedType = _resolvedType; requiredPermission = _requiredPermission; appOp = _appOp; receivers = _receivers; diff --git a/services/java/com/android/server/am/CompatModeDialog.java b/services/java/com/android/server/am/CompatModeDialog.java index 0442bda40d51..202cc7cad79d 100644 --- a/services/java/com/android/server/am/CompatModeDialog.java +++ b/services/java/com/android/server/am/CompatModeDialog.java @@ -28,7 +28,7 @@ import android.widget.CheckBox; import android.widget.CompoundButton; import android.widget.Switch; -public class CompatModeDialog extends Dialog { +public final class CompatModeDialog extends Dialog { final ActivityManagerService mService; final ApplicationInfo mAppInfo; diff --git a/services/java/com/android/server/am/CompatModePackages.java b/services/java/com/android/server/am/CompatModePackages.java index 3a6492e2957b..59e678738444 100644 --- a/services/java/com/android/server/am/CompatModePackages.java +++ b/services/java/com/android/server/am/CompatModePackages.java @@ -15,7 +15,6 @@ import com.android.internal.util.FastXmlSerializer; import android.app.ActivityManager; import android.app.AppGlobals; -import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.res.CompatibilityInfo; @@ -26,7 +25,7 @@ import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; -public class CompatModePackages { +public final class CompatModePackages { private final String TAG = ActivityManagerService.TAG; private final boolean DEBUG_CONFIGURATION = ActivityManagerService.DEBUG_CONFIGURATION; @@ -166,7 +165,7 @@ public class CompatModePackages { } public boolean getFrontActivityAskCompatModeLocked() { - ActivityRecord r = mService.mMainStack.topRunningActivityLocked(null); + ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(null); if (r == null) { return false; } @@ -178,7 +177,7 @@ public class CompatModePackages { } public void setFrontActivityAskCompatModeLocked(boolean ask) { - ActivityRecord r = mService.mMainStack.topRunningActivityLocked(null); + ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(null); if (r != null) { setPackageAskCompatModeLocked(r.packageName, ask); } @@ -200,7 +199,7 @@ public class CompatModePackages { } public int getFrontActivityScreenCompatModeLocked() { - ActivityRecord r = mService.mMainStack.topRunningActivityLocked(null); + ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(null); if (r == null) { return ActivityManager.COMPAT_MODE_UNKNOWN; } @@ -208,7 +207,7 @@ public class CompatModePackages { } public void setFrontActivityScreenCompatModeLocked(int mode) { - ActivityRecord r = mService.mMainStack.topRunningActivityLocked(null); + ActivityRecord r = mService.getFocusedStack().topRunningActivityLocked(null); if (r == null) { Slog.w(TAG, "setFrontActivityScreenCompatMode failed: no top activity"); return; @@ -295,25 +294,13 @@ public class CompatModePackages { Message msg = mHandler.obtainMessage(MSG_WRITE); mHandler.sendMessageDelayed(msg, 10000); - ActivityRecord starting = mService.mMainStack.topRunningActivityLocked(null); - - // All activities that came from the package must be - // restarted as if there was a config change. - for (int i=mService.mMainStack.mHistory.size()-1; i>=0; i--) { - ActivityRecord a = (ActivityRecord)mService.mMainStack.mHistory.get(i); - if (a.info.packageName.equals(packageName)) { - a.forceNewConfig = true; - if (starting != null && a == starting && a.visible) { - a.startFreezingScreenLocked(starting.app, - ActivityInfo.CONFIG_SCREEN_LAYOUT); - } - } - } + final ActivityStack stack = mService.getFocusedStack(); + ActivityRecord starting = stack.restartPackage(packageName); // Tell all processes that loaded this package about the change. for (int i=mService.mLruProcesses.size()-1; i>=0; i--) { ProcessRecord app = mService.mLruProcesses.get(i); - if (!app.pkgList.contains(packageName)) { + if (!app.pkgList.containsKey(packageName)) { continue; } try { @@ -327,10 +314,10 @@ public class CompatModePackages { } if (starting != null) { - mService.mMainStack.ensureActivityConfigurationLocked(starting, 0); + stack.ensureActivityConfigurationLocked(starting, 0); // And we need to make sure at this point that all other activities // are made visible with the correct configuration. - mService.mMainStack.ensureActivitiesVisibleLocked(starting, 0); + stack.ensureActivitiesVisibleLocked(starting, 0); } } } diff --git a/services/java/com/android/server/am/ConnectionRecord.java b/services/java/com/android/server/am/ConnectionRecord.java index 4ed3c3150b5a..576adc26a579 100644 --- a/services/java/com/android/server/am/ConnectionRecord.java +++ b/services/java/com/android/server/am/ConnectionRecord.java @@ -25,7 +25,7 @@ import java.io.PrintWriter; /** * Description of a single binding to a service. */ -class ConnectionRecord { +final class ConnectionRecord { final AppBindRecord binding; // The application/service binding. final ActivityRecord activity; // If non-null, the owning activity. final IServiceConnection conn; // The client connection. @@ -89,12 +89,15 @@ class ConnectionRecord { if ((flags&Context.BIND_ADJUST_WITH_ACTIVITY) != 0) { sb.append("ACT "); } - if ((flags&Context.BIND_NOT_VISIBLE) != 0) { - sb.append("!VIS "); - } if ((flags&Context.BIND_VISIBLE) != 0) { sb.append("VIS "); } + if ((flags&Context.BIND_SHOWING_UI) != 0) { + sb.append("UI "); + } + if ((flags&Context.BIND_NOT_VISIBLE) != 0) { + sb.append("!VIS "); + } if (serviceDead) { sb.append("DEAD "); } diff --git a/services/java/com/android/server/am/ContentProviderConnection.java b/services/java/com/android/server/am/ContentProviderConnection.java index 7f69b2467868..f2c9e2fd2297 100644 --- a/services/java/com/android/server/am/ContentProviderConnection.java +++ b/services/java/com/android/server/am/ContentProviderConnection.java @@ -23,7 +23,7 @@ import android.util.TimeUtils; /** * Represents a link between a content provider and client. */ -public class ContentProviderConnection extends Binder { +public final class ContentProviderConnection extends Binder { public final ContentProviderRecord provider; public final ProcessRecord client; public final long createTime; diff --git a/services/java/com/android/server/am/ContentProviderRecord.java b/services/java/com/android/server/am/ContentProviderRecord.java index 8fb6a93c5ed9..646b7d2994de 100644 --- a/services/java/com/android/server/am/ContentProviderRecord.java +++ b/services/java/com/android/server/am/ContentProviderRecord.java @@ -33,7 +33,7 @@ import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; -class ContentProviderRecord { +final class ContentProviderRecord { final ActivityManagerService service; public final ProviderInfo info; final int uid; diff --git a/services/java/com/android/server/am/CoreSettingsObserver.java b/services/java/com/android/server/am/CoreSettingsObserver.java index 585cf2b3f25f..10ea67c34465 100644 --- a/services/java/com/android/server/am/CoreSettingsObserver.java +++ b/services/java/com/android/server/am/CoreSettingsObserver.java @@ -33,7 +33,7 @@ import java.util.Map; * disk I/O operations. Note: This class assumes that all core settings reside * in {@link Settings.Secure}. */ -class CoreSettingsObserver extends ContentObserver { +final class CoreSettingsObserver extends ContentObserver { private static final String LOG_TAG = CoreSettingsObserver.class.getSimpleName(); // mapping form property name to its type diff --git a/services/java/com/android/server/am/DeviceMonitor.java b/services/java/com/android/server/am/DeviceMonitor.java deleted file mode 100644 index 21e7252bf24d..000000000000 --- a/services/java/com/android/server/am/DeviceMonitor.java +++ /dev/null @@ -1,234 +0,0 @@ -/* - * Copyright (C) 2008 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.am; - -import android.os.SELinux; -import android.util.Slog; - -import java.io.*; -import java.util.Arrays; - -/** - * Monitors device resources periodically for some period of time. Useful for - * tracking down performance problems. - */ -class DeviceMonitor { - - private static final String LOG_TAG = DeviceMonitor.class.getName(); - - /** Number of samples to take. */ - private static final int SAMPLE_COUNT = 10; - - /** Time to wait in ms between samples. */ - private static final int INTERVAL = 1000; - - /** Time to wait in ms between samples. */ - private static final int MAX_FILES = 30; - - private final byte[] buffer = new byte[1024]; - - /** Is the monitor currently running? */ - private boolean running = false; - - private DeviceMonitor() { - new Thread() { - public void run() { - monitor(); - } - }.start(); - } - - /** - * Loops continuously. Pauses until someone tells us to start monitoring. - */ - @SuppressWarnings("InfiniteLoopStatement") - private void monitor() { - while (true) { - waitForStart(); - - purge(); - - for (int i = 0; i < SAMPLE_COUNT; i++) { - try { - dump(); - } catch (IOException e) { - Slog.w(LOG_TAG, "Dump failed.", e); - } - pause(); - } - - stop(); - } - } - - private static final File PROC = new File("/proc"); - private static final File BASE = new File("/data/anr/"); - static { - if (!BASE.isDirectory() && !BASE.mkdirs()) { - throw new AssertionError("Couldn't create " + BASE + "."); - } - if (!SELinux.restorecon(BASE)) { - throw new AssertionError("Couldn't restorecon " + BASE + "."); - } - } - - private static final File[] PATHS = { - new File(PROC, "zoneinfo"), - new File(PROC, "interrupts"), - new File(PROC, "meminfo"), - new File(PROC, "slabinfo"), - }; - - - /** - * Deletes old files. - */ - private void purge() { - File[] files = BASE.listFiles(); - int count = files.length - MAX_FILES; - if (count > 0) { - Arrays.sort(files); - for (int i = 0; i < count; i++) { - if (!files[i].delete()) { - Slog.w(LOG_TAG, "Couldn't delete " + files[i] + "."); - } - } - } - } - - /** - * Dumps the current device stats to a new file. - */ - private void dump() throws IOException { - OutputStream out = new FileOutputStream( - new File(BASE, String.valueOf(System.currentTimeMillis()))); - try { - // Copy /proc/*/stat - for (File processDirectory : PROC.listFiles()) { - if (isProcessDirectory(processDirectory)) { - dump(new File(processDirectory, "stat"), out); - } - } - - // Copy other files. - for (File file : PATHS) { - dump(file, out); - } - } finally { - closeQuietly(out); - } - } - - /** - * Returns true if the given file represents a process directory. - */ - private static boolean isProcessDirectory(File file) { - try { - Integer.parseInt(file.getName()); - return file.isDirectory(); - } catch (NumberFormatException e) { - return false; - } - } - - /** - * Copies from a file to an output stream. - */ - private void dump(File from, OutputStream out) throws IOException { - writeHeader(from, out); - - FileInputStream in = null; - try { - in = new FileInputStream(from); - int count; - while ((count = in.read(buffer)) != -1) { - out.write(buffer, 0, count); - } - } finally { - closeQuietly(in); - } - } - - /** - * Writes a header for the given file. - */ - private static void writeHeader(File file, OutputStream out) - throws IOException { - String header = "*** " + file.toString() + "\n"; - out.write(header.getBytes()); - } - - /** - * Closes the given resource. Logs exceptions. - * @param closeable - */ - private static void closeQuietly(Closeable closeable) { - try { - if (closeable != null) { - closeable.close(); - } - } catch (IOException e) { - Slog.w(LOG_TAG, e); - } - } - - /** - * Pauses momentarily before we start the next dump. - */ - private void pause() { - try { - Thread.sleep(INTERVAL); - } catch (InterruptedException e) { /* ignore */ } - } - - /** - * Stops dumping. - */ - private synchronized void stop() { - running = false; - } - - /** - * Waits until someone starts us. - */ - private synchronized void waitForStart() { - while (!running) { - try { - wait(); - } catch (InterruptedException e) { /* ignore */ } - } - } - - /** - * Instructs the monitoring to start if it hasn't already. - */ - private synchronized void startMonitoring() { - if (!running) { - running = true; - notifyAll(); - } - } - - private static DeviceMonitor instance = new DeviceMonitor(); - - /** - * Starts monitoring if it hasn't started already. - */ - static void start() { - instance.startMonitoring(); - } -} diff --git a/services/java/com/android/server/am/EventLogTags.logtags b/services/java/com/android/server/am/EventLogTags.logtags index f784861a2990..d3cc56b53028 100644 --- a/services/java/com/android/server/am/EventLogTags.logtags +++ b/services/java/com/android/server/am/EventLogTags.logtags @@ -63,7 +63,7 @@ option java_package com.android.server.am 30024 am_broadcast_discard_filter (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(BroadcastFilter|1|5) 30025 am_broadcast_discard_app (User|1|5),(Broadcast|1|5),(Action|3),(Receiver Number|1|1),(App|3) # A service is being created -30030 am_create_service (User|1|5),(Service Record|1|5),(Name|3),(PID|1|5) +30030 am_create_service (User|1|5),(Service Record|1|5),(Name|3),(UID|1|5),(PID|1|5) # A service is being destroyed 30031 am_destroy_service (User|1|5),(Service Record|1|5),(PID|1|5) # A process has crashed too many times, it is being cleared @@ -86,3 +86,6 @@ option java_package com.android.server.am # User switched 30041 am_switch_user (id|1|5) + +# Activity fully drawn time +30042 am_activity_fully_drawn_time (User|1|5),(Token|1|5),(Component Name|3),(time|2|3) diff --git a/services/java/com/android/server/am/FactoryErrorDialog.java b/services/java/com/android/server/am/FactoryErrorDialog.java index 0ffb5889569b..f4632c135dfc 100644 --- a/services/java/com/android/server/am/FactoryErrorDialog.java +++ b/services/java/com/android/server/am/FactoryErrorDialog.java @@ -22,7 +22,7 @@ import android.os.Handler; import android.os.Message; import android.view.WindowManager; -class FactoryErrorDialog extends BaseErrorDialog { +final class FactoryErrorDialog extends BaseErrorDialog { public FactoryErrorDialog(Context context, CharSequence msg) { super(context); setCancelable(false); diff --git a/services/java/com/android/server/am/IntentBindRecord.java b/services/java/com/android/server/am/IntentBindRecord.java index 0a929648cf47..21cf266c62b1 100644 --- a/services/java/com/android/server/am/IntentBindRecord.java +++ b/services/java/com/android/server/am/IntentBindRecord.java @@ -19,6 +19,7 @@ package com.android.server.am; import android.content.Context; import android.content.Intent; import android.os.IBinder; +import android.util.ArrayMap; import java.io.PrintWriter; import java.util.HashMap; @@ -27,14 +28,14 @@ import java.util.Iterator; /** * A particular Intent that has been bound to a Service. */ -class IntentBindRecord { +final class IntentBindRecord { /** The running service. */ final ServiceRecord service; /** The intent that is bound.*/ final Intent.FilterComparison intent; // /** All apps that have bound to this Intent. */ - final HashMap<ProcessRecord, AppBindRecord> apps - = new HashMap<ProcessRecord, AppBindRecord>(); + final ArrayMap<ProcessRecord, AppBindRecord> apps + = new ArrayMap<ProcessRecord, AppBindRecord>(); /** Binder published from service. */ IBinder binder; /** Set when we have initiated a request for this binder. */ @@ -62,15 +63,12 @@ class IntentBindRecord { pw.print(" received="); pw.print(received); pw.print(" hasBound="); pw.print(hasBound); pw.print(" doRebind="); pw.println(doRebind); - if (apps.size() > 0) { - Iterator<AppBindRecord> it = apps.values().iterator(); - while (it.hasNext()) { - AppBindRecord a = it.next(); - pw.print(prefix); pw.print("* Client AppBindRecord{"); - pw.print(Integer.toHexString(System.identityHashCode(a))); - pw.print(' '); pw.print(a.client); pw.println('}'); - a.dumpInIntentBind(pw, prefix + " "); - } + for (int i=0; i<apps.size(); i++) { + AppBindRecord a = apps.valueAt(i); + pw.print(prefix); pw.print("* Client AppBindRecord{"); + pw.print(Integer.toHexString(System.identityHashCode(a))); + pw.print(' '); pw.print(a.client); pw.println('}'); + a.dumpInIntentBind(pw, prefix + " "); } } @@ -81,12 +79,11 @@ class IntentBindRecord { int collectFlags() { int flags = 0; - if (apps.size() > 0) { - for (AppBindRecord app : apps.values()) { - if (app.connections.size() > 0) { - for (ConnectionRecord conn : app.connections) { - flags |= conn.flags; - } + for (int i=apps.size()-1; i>=0; i--) { + AppBindRecord app = apps.valueAt(i); + if (app.connections.size() > 0) { + for (ConnectionRecord conn : app.connections) { + flags |= conn.flags; } } } diff --git a/services/java/com/android/server/am/LaunchWarningWindow.java b/services/java/com/android/server/am/LaunchWarningWindow.java index cb2b7bbeaff2..30c306655af0 100644 --- a/services/java/com/android/server/am/LaunchWarningWindow.java +++ b/services/java/com/android/server/am/LaunchWarningWindow.java @@ -26,7 +26,7 @@ import android.view.WindowManager; import android.widget.ImageView; import android.widget.TextView; -public class LaunchWarningWindow extends Dialog { +public final class LaunchWarningWindow extends Dialog { public LaunchWarningWindow(Context context, ActivityRecord cur, ActivityRecord next) { super(context, R.style.Theme_Toast); diff --git a/services/java/com/android/server/am/NativeCrashListener.java b/services/java/com/android/server/am/NativeCrashListener.java index a9454bd4a4be..2c7f1f13098b 100644 --- a/services/java/com/android/server/am/NativeCrashListener.java +++ b/services/java/com/android/server/am/NativeCrashListener.java @@ -40,7 +40,7 @@ import java.net.InetUnixAddress; * * Note that this component runs in a separate thread. */ -class NativeCrashListener extends Thread { +final class NativeCrashListener extends Thread { static final String TAG = "NativeCrashListener"; static final boolean DEBUG = false; static final boolean MORE_DEBUG = DEBUG && false; @@ -152,6 +152,13 @@ class NativeCrashListener extends Thread { Slog.d(TAG, "Exception writing ack: " + e.getMessage()); } } + try { + Libcore.os.close(peerFd); + } catch (ErrnoException e) { + if (MORE_DEBUG) { + Slog.d(TAG, "Exception closing socket: " + e.getMessage()); + } + } } } } diff --git a/services/java/com/android/server/am/PendingIntentRecord.java b/services/java/com/android/server/am/PendingIntentRecord.java index 8ab71dd2c2a3..17f24a928b90 100644 --- a/services/java/com/android/server/am/PendingIntentRecord.java +++ b/services/java/com/android/server/am/PendingIntentRecord.java @@ -31,7 +31,7 @@ import android.util.Slog; import java.io.PrintWriter; import java.lang.ref.WeakReference; -class PendingIntentRecord extends IIntentSender.Stub { +final class PendingIntentRecord extends IIntentSender.Stub { final ActivityManagerService owner; final Key key; final int uid; @@ -259,7 +259,7 @@ class PendingIntentRecord extends IIntentSender.Stub { } break; case ActivityManager.INTENT_SENDER_ACTIVITY_RESULT: - key.activity.stack.sendActivityResultLocked(-1, key.activity, + key.activity.task.stack.sendActivityResultLocked(-1, key.activity, key.who, key.requestCode, code, finalIntent); break; case ActivityManager.INTENT_SENDER_BROADCAST: diff --git a/services/java/com/android/server/am/PendingThumbnailsRecord.java b/services/java/com/android/server/am/PendingThumbnailsRecord.java index ed478c9741c8..e4eb4d04c155 100644 --- a/services/java/com/android/server/am/PendingThumbnailsRecord.java +++ b/services/java/com/android/server/am/PendingThumbnailsRecord.java @@ -24,16 +24,16 @@ import java.util.HashSet; * This class keeps track of calls to getTasks() that are still * waiting for thumbnail images. */ -class PendingThumbnailsRecord +final class PendingThumbnailsRecord { final IThumbnailReceiver receiver; // who is waiting. - HashSet pendingRecords; // HistoryRecord objects we still wait for. + final HashSet<ActivityRecord> pendingRecords; // HistoryRecord objects we still wait for. boolean finished; // Is pendingRecords empty? PendingThumbnailsRecord(IThumbnailReceiver _receiver) { receiver = _receiver; - pendingRecords = new HashSet(); + pendingRecords = new HashSet<ActivityRecord>(); finished = false; } } diff --git a/services/java/com/android/server/am/ProcessList.java b/services/java/com/android/server/am/ProcessList.java index 1a635a9a14a0..d3777c7ae134 100644 --- a/services/java/com/android/server/am/ProcessList.java +++ b/services/java/com/android/server/am/ProcessList.java @@ -19,27 +19,35 @@ package com.android.server.am; import java.io.FileOutputStream; import java.io.IOException; +import android.app.ActivityManager; import com.android.internal.util.MemInfoReader; import com.android.server.wm.WindowManagerService; +import android.content.res.Resources; import android.graphics.Point; +import android.os.SystemProperties; import android.util.Slog; import android.view.Display; /** * Activity manager code dealing with processes. */ -class ProcessList { +final class ProcessList { // The minimum time we allow between crashes, for us to consider this // application to be bad and stop and its services and reject broadcasts. static final int MIN_CRASH_INTERVAL = 60*1000; // OOM adjustments for processes in various states: + // Adjustment used in certain places where we don't know it yet. + // (Generally this is something that is going to be cached, but we + // don't know the exact value in the cached range to assign yet.) + static final int UNKNOWN_ADJ = 16; + // This is a process only hosting activities that are not visible, // so it can be killed without any disruption. - static final int HIDDEN_APP_MAX_ADJ = 15; - static int HIDDEN_APP_MIN_ADJ = 9; + static final int CACHED_APP_MAX_ADJ = 15; + static final int CACHED_APP_MIN_ADJ = 9; // The B list of SERVICE_ADJ -- these are the old and decrepit // services that aren't as shiny and interesting as the ones in the A list. @@ -62,14 +70,14 @@ class ProcessList { // have much of an impact as far as the user is concerned. static final int SERVICE_ADJ = 5; - // This is a process currently hosting a backup operation. Killing it - // is not entirely fatal but is generally a bad idea. - static final int BACKUP_APP_ADJ = 4; - // This is a process with a heavy-weight application. It is in the // background, but we want to try to avoid killing it. Value set in // system/rootdir/init.rc on startup. - static final int HEAVY_WEIGHT_APP_ADJ = 3; + static final int HEAVY_WEIGHT_APP_ADJ = 4; + + // This is a process currently hosting a backup operation. Killing it + // is not entirely fatal but is generally a bad idea. + static final int BACKUP_APP_ADJ = 3; // This is a process only hosting components that are perceptible to the // user, and we really want to avoid killing them, but they are not @@ -91,49 +99,54 @@ class ProcessList { // The system process runs at the default adjustment. static final int SYSTEM_ADJ = -16; + // Special code for native processes that are not being managed by the system (so + // don't have an oom adj assigned by the system). + static final int NATIVE_ADJ = -17; + // Memory pages are 4K. static final int PAGE_SIZE = 4*1024; - // The minimum number of hidden apps we want to be able to keep around, + // The minimum number of cached apps we want to be able to keep around, // without empty apps being able to push them out of memory. - static final int MIN_HIDDEN_APPS = 2; - - // The maximum number of hidden processes we will keep around before - // killing them; this is just a control to not let us go too crazy with - // keeping around processes on devices with large amounts of RAM. - static final int MAX_HIDDEN_APPS = 24; + static final int MIN_CACHED_APPS = 2; + + // The maximum number of cached processes we will keep around before killing them. + // NOTE: this constant is *only* a control to not let us go too crazy with + // keeping around processes on devices with large amounts of RAM. For devices that + // are tighter on RAM, the out of memory killer is responsible for killing background + // processes as RAM is needed, and we should *never* be relying on this limit to + // kill them. Also note that this limit only applies to cached background processes; + // we have no limit on the number of service, visible, foreground, or other such + // processes and the number of those processes does not count against the cached + // process limit. + static final int MAX_CACHED_APPS = 24; // We allow empty processes to stick around for at most 30 minutes. static final long MAX_EMPTY_TIME = 30*60*1000; - // The number of hidden at which we don't consider it necessary to do - // memory trimming. - static final int TRIM_HIDDEN_APPS = 3; + // The maximum number of empty app processes we will let sit around. + private static final int MAX_EMPTY_APPS = computeEmptyProcessLimit(MAX_CACHED_APPS); // The number of empty apps at which we don't consider it necessary to do // memory trimming. - static final int TRIM_EMPTY_APPS = 3; + static final int TRIM_EMPTY_APPS = MAX_EMPTY_APPS/2; + + // The number of cached at which we don't consider it necessary to do + // memory trimming. + static final int TRIM_CACHED_APPS = ((MAX_CACHED_APPS-MAX_EMPTY_APPS)*2)/3; - // Threshold of number of hidden+empty where we consider memory critical. + // Threshold of number of cached+empty where we consider memory critical. static final int TRIM_CRITICAL_THRESHOLD = 3; - // Threshold of number of hidden+empty where we consider memory critical. + // Threshold of number of cached+empty where we consider memory critical. static final int TRIM_LOW_THRESHOLD = 5; - // We put empty content processes after any hidden processes that have - // been idle for less than 15 seconds. - static final long CONTENT_APP_IDLE_OFFSET = 15*1000; - - // We put empty content processes after any hidden processes that have - // been idle for less than 120 seconds. - static final long EMPTY_APP_IDLE_OFFSET = 120*1000; - // These are the various interesting memory levels that we will give to // the OOM killer. Note that the OOM killer only supports 6 slots, so we // can't give it a different value for every possible kind of process. private final int[] mOomAdj = new int[] { FOREGROUND_APP_ADJ, VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ, - BACKUP_APP_ADJ, HIDDEN_APP_MIN_ADJ, HIDDEN_APP_MAX_ADJ + BACKUP_APP_ADJ, CACHED_APP_MIN_ADJ, CACHED_APP_MAX_ADJ }; // These are the low-end OOM level limits. This is appropriate for an // HVGA or smaller phone with less than 512MB. Values are in KB. @@ -152,6 +165,8 @@ class ProcessList { private final long mTotalMemMb; + private long mCachedRestoreLevel; + private boolean mHaveDisplaySize; ProcessList() { @@ -164,7 +179,7 @@ class ProcessList { void applyDisplaySize(WindowManagerService wm) { if (!mHaveDisplaySize) { Point p = new Point(); - wm.getInitialDisplaySize(Display.DEFAULT_DISPLAY, p); + wm.getBaseDisplaySize(Display.DEFAULT_DISPLAY, p); if (p.x != 0 && p.y != 0) { updateOomLevels(p.x, p.y, true); mHaveDisplaySize = true; @@ -178,10 +193,14 @@ class ProcessList { float scaleMem = ((float)(mTotalMemMb-300))/(700-300); // Scale buckets from screen size. - int minSize = 320*480; // 153600 + int minSize = 480*800; // 384000 int maxSize = 1280*800; // 1024000 230400 870400 .264 float scaleDisp = ((float)(displayWidth*displayHeight)-minSize)/(maxSize-minSize); - //Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth + " dh=" + displayHeight); + if (false) { + Slog.i("XXXXXX", "scaleMem=" + scaleMem); + Slog.i("XXXXXX", "scaleDisp=" + scaleDisp + " dw=" + displayWidth + + " dh=" + displayHeight); + } StringBuilder adjString = new StringBuilder(); StringBuilder memString = new StringBuilder(); @@ -189,11 +208,41 @@ class ProcessList { float scale = scaleMem > scaleDisp ? scaleMem : scaleDisp; if (scale < 0) scale = 0; else if (scale > 1) scale = 1; + int minfree_adj = Resources.getSystem().getInteger( + com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAdjust); + int minfree_abs = Resources.getSystem().getInteger( + com.android.internal.R.integer.config_lowMemoryKillerMinFreeKbytesAbsolute); + if (false) { + Slog.i("XXXXXX", "minfree_adj=" + minfree_adj + " minfree_abs=" + minfree_abs); + } + for (int i=0; i<mOomAdj.length; i++) { long low = mOomMinFreeLow[i]; long high = mOomMinFreeHigh[i]; mOomMinFree[i] = (long)(low + ((high-low)*scale)); + } + + if (minfree_abs >= 0) { + for (int i=0; i<mOomAdj.length; i++) { + mOomMinFree[i] = (long)((float)minfree_abs * mOomMinFree[i] / mOomMinFree[mOomAdj.length - 1]); + } + } + if (minfree_adj != 0) { + for (int i=0; i<mOomAdj.length; i++) { + mOomMinFree[i] += (long)((float)minfree_adj * mOomMinFree[i] / mOomMinFree[mOomAdj.length - 1]); + if (mOomMinFree[i] < 0) { + mOomMinFree[i] = 0; + } + } + } + + // The maximum size we will restore a process from cached to background, when under + // memory duress, is 1/3 the size we have reserved for kernel caches and other overhead + // before killing background processes. + mCachedRestoreLevel = (getMemLevel(ProcessList.CACHED_APP_MAX_ADJ)/1024) / 3; + + for (int i=0; i<mOomAdj.length; i++) { if (i > 0) { adjString.append(','); memString.append(','); @@ -202,15 +251,244 @@ class ProcessList { memString.append((mOomMinFree[i]*1024)/PAGE_SIZE); } + // Ask the kernel to try to keep enough memory free to allocate 3 full + // screen 32bpp buffers without entering direct reclaim. + int reserve = displayWidth * displayHeight * 4 * 3 / 1024; + int reserve_adj = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAdjust); + int reserve_abs = Resources.getSystem().getInteger(com.android.internal.R.integer.config_extraFreeKbytesAbsolute); + + if (reserve_abs >= 0) { + reserve = reserve_abs; + } + + if (reserve_adj != 0) { + reserve += reserve_adj; + if (reserve < 0) { + reserve = 0; + } + } + //Slog.i("XXXXXXX", "******************************* MINFREE: " + memString); if (write) { writeFile("/sys/module/lowmemorykiller/parameters/adj", adjString.toString()); writeFile("/sys/module/lowmemorykiller/parameters/minfree", memString.toString()); + SystemProperties.set("sys.sysctl.extra_free_kbytes", Integer.toString(reserve)); } // GB: 2048,3072,4096,6144,7168,8192 // HC: 8192,10240,12288,14336,16384,20480 } + public static int computeEmptyProcessLimit(int totalProcessLimit) { + return (totalProcessLimit*2)/3; + } + + private static String buildOomTag(String prefix, String space, int val, int base) { + if (val == base) { + if (space == null) return prefix; + return prefix + " "; + } + return prefix + "+" + Integer.toString(val-base); + } + + public static String makeOomAdjString(int setAdj) { + if (setAdj >= ProcessList.CACHED_APP_MIN_ADJ) { + return buildOomTag("cch", " ", setAdj, ProcessList.CACHED_APP_MIN_ADJ); + } else if (setAdj >= ProcessList.SERVICE_B_ADJ) { + return buildOomTag("svcb ", null, setAdj, ProcessList.SERVICE_B_ADJ); + } else if (setAdj >= ProcessList.PREVIOUS_APP_ADJ) { + return buildOomTag("prev ", null, setAdj, ProcessList.PREVIOUS_APP_ADJ); + } else if (setAdj >= ProcessList.HOME_APP_ADJ) { + return buildOomTag("home ", null, setAdj, ProcessList.HOME_APP_ADJ); + } else if (setAdj >= ProcessList.SERVICE_ADJ) { + return buildOomTag("svc ", null, setAdj, ProcessList.SERVICE_ADJ); + } else if (setAdj >= ProcessList.HEAVY_WEIGHT_APP_ADJ) { + return buildOomTag("hvy ", null, setAdj, ProcessList.HEAVY_WEIGHT_APP_ADJ); + } else if (setAdj >= ProcessList.BACKUP_APP_ADJ) { + return buildOomTag("bkup ", null, setAdj, ProcessList.BACKUP_APP_ADJ); + } else if (setAdj >= ProcessList.PERCEPTIBLE_APP_ADJ) { + return buildOomTag("prcp ", null, setAdj, ProcessList.PERCEPTIBLE_APP_ADJ); + } else if (setAdj >= ProcessList.VISIBLE_APP_ADJ) { + return buildOomTag("vis ", null, setAdj, ProcessList.VISIBLE_APP_ADJ); + } else if (setAdj >= ProcessList.FOREGROUND_APP_ADJ) { + return buildOomTag("fore ", null, setAdj, ProcessList.FOREGROUND_APP_ADJ); + } else if (setAdj >= ProcessList.PERSISTENT_PROC_ADJ) { + return buildOomTag("pers ", null, setAdj, ProcessList.PERSISTENT_PROC_ADJ); + } else if (setAdj >= ProcessList.SYSTEM_ADJ) { + return buildOomTag("sys ", null, setAdj, ProcessList.SYSTEM_ADJ); + } else if (setAdj >= ProcessList.NATIVE_ADJ) { + return buildOomTag("ntv ", null, setAdj, ProcessList.NATIVE_ADJ); + } else { + return Integer.toString(setAdj); + } + } + + public static String makeProcStateString(int curProcState) { + String procState; + switch (curProcState) { + case -1: + procState = "N "; + break; + case ActivityManager.PROCESS_STATE_PERSISTENT: + procState = "P "; + break; + case ActivityManager.PROCESS_STATE_PERSISTENT_UI: + procState = "PU"; + break; + case ActivityManager.PROCESS_STATE_TOP: + procState = "T "; + break; + case ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND: + procState = "IF"; + break; + case ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND: + procState = "IB"; + break; + case ActivityManager.PROCESS_STATE_BACKUP: + procState = "BU"; + break; + case ActivityManager.PROCESS_STATE_HEAVY_WEIGHT: + procState = "HW"; + break; + case ActivityManager.PROCESS_STATE_SERVICE: + procState = "S "; + break; + case ActivityManager.PROCESS_STATE_RECEIVER: + procState = "R "; + break; + case ActivityManager.PROCESS_STATE_HOME: + procState = "HO"; + break; + case ActivityManager.PROCESS_STATE_LAST_ACTIVITY: + procState = "LA"; + break; + case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY: + procState = "CA"; + break; + case ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT: + procState = "Ca"; + break; + case ActivityManager.PROCESS_STATE_CACHED_EMPTY: + procState = "CE"; + break; + default: + procState = "??"; + break; + } + return procState; + } + + public static void appendRamKb(StringBuilder sb, long ramKb) { + for (int j=0, fact=10; j<6; j++, fact*=10) { + if (ramKb < fact) { + sb.append(' '); + } + } + sb.append(ramKb); + } + + // The minimum amount of time after a state change it is safe ro collect PSS. + public static final int PSS_MIN_TIME_FROM_STATE_CHANGE = 15*1000; + + // The maximum amount of time we want to go between PSS collections. + public static final int PSS_MAX_INTERVAL = 30*60*1000; + + // The minimum amount of time between successive PSS requests for *all* processes. + public static final int PSS_ALL_INTERVAL = 10*60*1000; + + // The minimum amount of time between successive PSS requests for a process. + private static final int PSS_SHORT_INTERVAL = 2*60*1000; + + // The amount of time until PSS when a process first becomes top. + private static final int PSS_FIRST_TOP_INTERVAL = 10*1000; + + // The amount of time until PSS when a process first goes into the background. + private static final int PSS_FIRST_BACKGROUND_INTERVAL = 20*1000; + + // The amount of time until PSS when a process first becomes cached. + private static final int PSS_FIRST_CACHED_INTERVAL = 30*1000; + + // The amount of time until PSS when an important process stays in the same state. + private static final int PSS_SAME_IMPORTANT_INTERVAL = 15*60*1000; + + // The amount of time until PSS when a service process stays in the same state. + private static final int PSS_SAME_SERVICE_INTERVAL = 20*60*1000; + + // The amount of time until PSS when a cached process stays in the same state. + private static final int PSS_SAME_CACHED_INTERVAL = 30*60*1000; + + public static final int PROC_MEM_PERSISTENT = 0; + public static final int PROC_MEM_TOP = 1; + public static final int PROC_MEM_IMPORTANT = 2; + public static final int PROC_MEM_SERVICE = 3; + public static final int PROC_MEM_CACHED = 4; + + private static final int[] sProcStateToProcMem = new int[] { + PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT + PROC_MEM_PERSISTENT, // ActivityManager.PROCESS_STATE_PERSISTENT_UI + PROC_MEM_TOP, // ActivityManager.PROCESS_STATE_TOP + PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND + PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND + PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_BACKUP + PROC_MEM_IMPORTANT, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT + PROC_MEM_SERVICE, // ActivityManager.PROCESS_STATE_SERVICE + PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_RECEIVER + PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_HOME + PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY + PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY + PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + PROC_MEM_CACHED, // ActivityManager.PROCESS_STATE_CACHED_EMPTY + }; + + private static final long[] sFirstAwakePssTimes = new long[] { + PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT + PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI + PSS_FIRST_TOP_INTERVAL, // ActivityManager.PROCESS_STATE_TOP + PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND + PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND + PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP + PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT + PSS_FIRST_BACKGROUND_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE + PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER + PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HOME + PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY + PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY + PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + PSS_FIRST_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY + }; + + private static final long[] sSameAwakePssTimes = new long[] { + PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT + PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_PERSISTENT_UI + PSS_SHORT_INTERVAL, // ActivityManager.PROCESS_STATE_TOP + PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND + PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND + PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_BACKUP + PSS_SAME_IMPORTANT_INTERVAL, // ActivityManager.PROCESS_STATE_HEAVY_WEIGHT + PSS_SAME_SERVICE_INTERVAL, // ActivityManager.PROCESS_STATE_SERVICE + PSS_SAME_SERVICE_INTERVAL, // ActivityManager.PROCESS_STATE_RECEIVER + PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_HOME + PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_LAST_ACTIVITY + PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY + PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_ACTIVITY_CLIENT + PSS_SAME_CACHED_INTERVAL, // ActivityManager.PROCESS_STATE_CACHED_EMPTY + }; + + public static boolean procStatesDifferForMem(int procState1, int procState2) { + return sProcStateToProcMem[procState1] != sProcStateToProcMem[procState2]; + } + + public static long computeNextPssTime(int procState, boolean first, boolean sleeping, + long now) { + final long[] table = sleeping + ? (first + ? sFirstAwakePssTimes + : sSameAwakePssTimes) + : (first + ? sFirstAwakePssTimes + : sSameAwakePssTimes); + return now + table[procState]; + } + long getMemLevel(int adjustment) { for (int i=0; i<mOomAdj.length; i++) { if (adjustment <= mOomAdj[i]) { @@ -220,6 +498,14 @@ class ProcessList { return mOomMinFree[mOomAdj.length-1] * 1024; } + /** + * Return the maximum pss size in kb that we consider a process acceptable to + * restore from its cached state for running in the background when RAM is low. + */ + long getCachedRestoreThresholdKb() { + return mCachedRestoreLevel; + } + private void writeFile(String path, String data) { FileOutputStream fos = null; try { diff --git a/services/java/com/android/server/am/ProcessMemInfo.java b/services/java/com/android/server/am/ProcessMemInfo.java new file mode 100644 index 000000000000..c94694e7c4b1 --- /dev/null +++ b/services/java/com/android/server/am/ProcessMemInfo.java @@ -0,0 +1,37 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +public class ProcessMemInfo { + final String name; + final int pid; + final int oomAdj; + final int procState; + final String adjType; + final String adjReason; + long pss; + + public ProcessMemInfo(String _name, int _pid, int _oomAdj, int _procState, + String _adjType, String _adjReason) { + name = _name; + pid = _pid; + oomAdj = _oomAdj; + procState = _procState; + adjType = _adjType; + adjReason = _adjReason; + } +} diff --git a/services/java/com/android/server/am/ProcessRecord.java b/services/java/com/android/server/am/ProcessRecord.java index 7929f96d2aee..486e9163b4fc 100644 --- a/services/java/com/android/server/am/ProcessRecord.java +++ b/services/java/com/android/server/am/ProcessRecord.java @@ -16,6 +16,8 @@ package com.android.server.am; +import android.util.ArraySet; +import com.android.internal.app.ProcessStats; import com.android.internal.os.BatteryStatsImpl; import android.app.ActivityManager; @@ -32,19 +34,18 @@ import android.os.IBinder; import android.os.Process; import android.os.SystemClock; import android.os.UserHandle; +import android.util.ArrayMap; import android.util.PrintWriterPrinter; import android.util.TimeUtils; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; /** * Full information about a particular process that * is currently running. */ -class ProcessRecord { +final class ProcessRecord { final BatteryStatsImpl.Uid.Proc batteryStats; // where to collect runtime statistics final ApplicationInfo info; // all about the first app in the process final boolean isolated; // true if this is a special isolated process @@ -52,32 +53,42 @@ class ProcessRecord { final int userId; // user of process. final String processName; // name of the process // List of packages running in the process - final HashSet<String> pkgList = new HashSet<String>(); + final ArrayMap<String, ProcessStats.ProcessState> pkgList + = new ArrayMap<String, ProcessStats.ProcessState>(); IApplicationThread thread; // the actual proc... may be null only if // 'persistent' is true (in which case we // are in the process of launching the app) + ProcessStats.ProcessState baseProcessTracker; int pid; // The process of this application; 0 if none boolean starting; // True if the process is being started long lastActivityTime; // For managing the LRU list - long lruWeight; // Weight for ordering in LRU list + long lastPssTime; // Last time we retrieved PSS data + long nextPssTime; // Next time we want to request PSS data + long lastStateTime; // Last time setProcState changed + long initialIdlePss; // Initial memory pss of process for idle maintenance. + long lastPss; // Last computed memory pss. + long lastCachedPss; // Last computed pss when in cached state. int maxAdj; // Maximum OOM adjustment for this process - int hiddenAdj; // If hidden, this is the adjustment to use - int clientHiddenAdj; // If empty but hidden client, this is the adjustment to use - int emptyAdj; // If empty, this is the adjustment to use int curRawAdj; // Current OOM unlimited adjustment for this process int setRawAdj; // Last set OOM unlimited adjustment for this process - int nonStoppingAdj; // Adjustment not counting any stopping activities int curAdj; // Current OOM adjustment for this process int setAdj; // Last set OOM adjustment for this process int curSchedGroup; // Currently desired scheduling class int setSchedGroup; // Last set to background scheduling class int trimMemoryLevel; // Last selected memory trimming level int memImportance; // Importance constant computed from curAdj + int curProcState = -1; // Currently computed process state: ActivityManager.PROCESS_STATE_* + int repProcState = -1; // Last reported process state + int setProcState = -1; // Last set process state in process tracker + int pssProcState = -1; // The proc state we are currently requesting pss for boolean serviceb; // Process currently is on the service B list + boolean serviceHighRam; // We are forcing to service B list due to its RAM use boolean keeping; // Actively running code so don't kill due to that? boolean setIsForeground; // Running foreground UI when last set? + boolean notCachedSinceIdle; // Has this process not been in a cached state since last idle? boolean hasActivities; // Are there any activities running in this process? boolean hasClientActivities; // Are there any client services with activities? + boolean hasStartedServices; // Are there any started services running in this process? boolean foregroundServices; // Running any services that are foreground? boolean foregroundActivities; // Running any activities that are foreground? boolean systemNoUi; // This is a system process, but not currently showing UI. @@ -85,8 +96,9 @@ class ProcessRecord { boolean pendingUiClean; // Want to clean up resources from showing UI? boolean hasAboveClient; // Bound using BIND_ABOVE_CLIENT, so want to be lower boolean bad; // True if disabled in the bad process list - boolean killedBackground; // True when proc has been killed due to too many bg - String waitingToKill; // Process is waiting to be killed when in the bg; reason + boolean killedByAm; // True when proc has been killed by activity manager, not for RAM + boolean procStateChanged; // Keep track of whether we changed 'setAdj'. + String waitingToKill; // Process is waiting to be killed when in the bg, and reason IBinder forcingToForeground;// Token that is forcing this process to be foreground int adjSeq; // Sequence id for identifying oom_adj assignment cycles int lruSeq; // Sequence id for identifying LRU update cycles @@ -108,8 +120,7 @@ class ProcessRecord { long lastLowMemory; // When we last told the app that memory is low boolean reportLowMemory; // Set to true when waiting to report low mem boolean empty; // Is this an empty background process? - boolean hidden; // Is this a hidden process? - int lastPss; // Last pss size reported by app. + boolean cached; // Is this a cached process? String adjType; // Debugging: primary thing impacting oom_adj. int adjTypeCode; // Debugging: adj code to report to app. Object adjSource; // Debugging: option dependent object. @@ -119,22 +130,23 @@ class ProcessRecord { // contains HistoryRecord objects final ArrayList<ActivityRecord> activities = new ArrayList<ActivityRecord>(); // all ServiceRecord running in this process - final HashSet<ServiceRecord> services = new HashSet<ServiceRecord>(); + final ArraySet<ServiceRecord> services = new ArraySet<ServiceRecord>(); // services that are currently executing code (need to remain foreground). - final HashSet<ServiceRecord> executingServices - = new HashSet<ServiceRecord>(); + final ArraySet<ServiceRecord> executingServices + = new ArraySet<ServiceRecord>(); // All ConnectionRecord this process holds - final HashSet<ConnectionRecord> connections - = new HashSet<ConnectionRecord>(); + final ArraySet<ConnectionRecord> connections + = new ArraySet<ConnectionRecord>(); // all IIntentReceivers that are registered from this process. - final HashSet<ReceiverList> receivers = new HashSet<ReceiverList>(); + final ArraySet<ReceiverList> receivers = new ArraySet<ReceiverList>(); // class (String) -> ContentProviderRecord - final HashMap<String, ContentProviderRecord> pubProviders - = new HashMap<String, ContentProviderRecord>(); + final ArrayMap<String, ContentProviderRecord> pubProviders + = new ArrayMap<String, ContentProviderRecord>(); // All ContentProviderRecord process is using final ArrayList<ContentProviderConnection> conProviders = new ArrayList<ContentProviderConnection>(); - + + boolean execServicesFg; // do we need to be executing services in the foreground? boolean persistent; // always keep this application running? boolean crashing; // are we in the process of crashing? Dialog crashDialog; // dialog being displayed due to crash. @@ -177,7 +189,12 @@ class ProcessRecord { pw.print(prefix); pw.print("dir="); pw.print(info.sourceDir); pw.print(" publicDir="); pw.print(info.publicSourceDir); pw.print(" data="); pw.println(info.dataDir); - pw.print(prefix); pw.print("packageList="); pw.println(pkgList); + pw.print(prefix); pw.print("packageList={"); + for (int i=0; i<pkgList.size(); i++) { + if (i > 0) pw.print(", "); + pw.print(pkgList.keyAt(i)); + } + pw.println("}"); pw.print(prefix); pw.print("compat="); pw.println(compat); if (instrumentationClass != null || instrumentationProfileFile != null || instrumentationArguments != null) { @@ -195,29 +212,45 @@ class ProcessRecord { } pw.print(prefix); pw.print("thread="); pw.println(thread); pw.print(prefix); pw.print("pid="); pw.print(pid); pw.print(" starting="); - pw.print(starting); pw.print(" lastPss="); pw.println(lastPss); + pw.println(starting); pw.print(prefix); pw.print("lastActivityTime="); TimeUtils.formatDuration(lastActivityTime, now, pw); - pw.print(" lruWeight="); pw.print(lruWeight); - pw.print(" serviceb="); pw.print(serviceb); - pw.print(" keeping="); pw.print(keeping); - pw.print(" hidden="); pw.print(hidden); + pw.print(" lastPssTime="); + TimeUtils.formatDuration(lastPssTime, now, pw); + pw.print(" nextPssTime="); + TimeUtils.formatDuration(nextPssTime, now, pw); + pw.println(); + pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq); + pw.print(" lruSeq="); pw.print(lruSeq); + pw.print(" lastPss="); pw.print(lastPss); + pw.print(" lastCachedPss="); pw.println(lastCachedPss); + pw.print(prefix); pw.print("keeping="); pw.print(keeping); + pw.print(" cached="); pw.print(cached); pw.print(" empty="); pw.println(empty); + if (serviceb) { + pw.print(prefix); pw.print("serviceb="); pw.print(serviceb); + pw.print(" serviceHighRam="); pw.println(serviceHighRam); + } + if (notCachedSinceIdle) { + pw.print(prefix); pw.print("notCachedSinceIdle="); pw.print(notCachedSinceIdle); + pw.print(" initialIdlePss="); pw.println(initialIdlePss); + } pw.print(prefix); pw.print("oom: max="); pw.print(maxAdj); - pw.print(" hidden="); pw.print(hiddenAdj); - pw.print(" client="); pw.print(clientHiddenAdj); - pw.print(" empty="); pw.print(emptyAdj); pw.print(" curRaw="); pw.print(curRawAdj); pw.print(" setRaw="); pw.print(setRawAdj); - pw.print(" nonStopping="); pw.print(nonStoppingAdj); pw.print(" cur="); pw.print(curAdj); pw.print(" set="); pw.println(setAdj); pw.print(prefix); pw.print("curSchedGroup="); pw.print(curSchedGroup); pw.print(" setSchedGroup="); pw.print(setSchedGroup); pw.print(" systemNoUi="); pw.print(systemNoUi); pw.print(" trimMemoryLevel="); pw.println(trimMemoryLevel); - pw.print(prefix); pw.print("adjSeq="); pw.print(adjSeq); - pw.print(" lruSeq="); pw.println(lruSeq); + pw.print(prefix); pw.print("curProcState="); pw.print(curProcState); + pw.print(" repProcState="); pw.print(repProcState); + pw.print(" pssProcState="); pw.print(pssProcState); + pw.print(" setProcState="); pw.print(setProcState); + pw.print(" lastStateTime="); + TimeUtils.formatDuration(lastStateTime, now, pw); + pw.println(); if (hasShownUi || pendingUiClean || hasAboveClient) { pw.print(prefix); pw.print("hasShownUi="); pw.print(hasShownUi); pw.print(" pendingUiClean="); pw.print(pendingUiClean); @@ -237,6 +270,9 @@ class ProcessRecord { pw.print(" hasClientActivities="); pw.print(hasClientActivities); pw.print(" foregroundActivities="); pw.println(foregroundActivities); } + if (hasStartedServices) { + pw.print(prefix); pw.print("hasStartedServices="); pw.println(hasStartedServices); + } if (!keeping) { long wtime; synchronized (batteryStats.getBatteryStats()) { @@ -256,8 +292,8 @@ class ProcessRecord { pw.print(" lastLowMemory="); TimeUtils.formatDuration(lastLowMemory, now, pw); pw.print(" reportLowMemory="); pw.println(reportLowMemory); - if (killedBackground || waitingToKill != null) { - pw.print(prefix); pw.print("killedBackground="); pw.print(killedBackground); + if (killedByAm || waitingToKill != null) { + pw.print(prefix); pw.print("killedByAm="); pw.print(killedByAm); pw.print(" waitingToKill="); pw.println(waitingToKill); } if (debugging || crashing || crashDialog != null || notResponding @@ -284,27 +320,28 @@ class ProcessRecord { } if (services.size() > 0) { pw.print(prefix); pw.println("Services:"); - for (ServiceRecord sr : services) { - pw.print(prefix); pw.print(" - "); pw.println(sr); + for (int i=0; i<services.size(); i++) { + pw.print(prefix); pw.print(" - "); pw.println(services.valueAt(i)); } } if (executingServices.size() > 0) { - pw.print(prefix); pw.println("Executing Services:"); - for (ServiceRecord sr : executingServices) { - pw.print(prefix); pw.print(" - "); pw.println(sr); + pw.print(prefix); pw.print("Executing Services (fg="); + pw.print(execServicesFg); pw.println(")"); + for (int i=0; i<executingServices.size(); i++) { + pw.print(prefix); pw.print(" - "); pw.println(executingServices.valueAt(i)); } } if (connections.size() > 0) { pw.print(prefix); pw.println("Connections:"); - for (ConnectionRecord cr : connections) { - pw.print(prefix); pw.print(" - "); pw.println(cr); + for (int i=0; i<connections.size(); i++) { + pw.print(prefix); pw.print(" - "); pw.println(connections.valueAt(i)); } } if (pubProviders.size() > 0) { pw.print(prefix); pw.println("Published Providers:"); - for (HashMap.Entry<String, ContentProviderRecord> ent : pubProviders.entrySet()) { - pw.print(prefix); pw.print(" - "); pw.println(ent.getKey()); - pw.print(prefix); pw.print(" -> "); pw.println(ent.getValue()); + for (int i=0; i<pubProviders.size(); i++) { + pw.print(prefix); pw.print(" - "); pw.println(pubProviders.keyAt(i)); + pw.print(prefix); pw.print(" -> "); pw.println(pubProviders.valueAt(i)); } } if (conProviders.size() > 0) { @@ -318,28 +355,27 @@ class ProcessRecord { } if (receivers.size() > 0) { pw.print(prefix); pw.println("Receivers:"); - for (ReceiverList rl : receivers) { - pw.print(prefix); pw.print(" - "); pw.println(rl); + for (int i=0; i<receivers.size(); i++) { + pw.print(prefix); pw.print(" - "); pw.println(receivers.valueAt(i)); } } } - ProcessRecord(BatteryStatsImpl.Uid.Proc _batteryStats, IApplicationThread _thread, - ApplicationInfo _info, String _processName, int _uid) { + ProcessRecord(BatteryStatsImpl.Uid.Proc _batteryStats, ApplicationInfo _info, + String _processName, int _uid) { batteryStats = _batteryStats; info = _info; isolated = _info.uid != _uid; uid = _uid; userId = UserHandle.getUserId(_uid); processName = _processName; - pkgList.add(_info.packageName); - thread = _thread; - maxAdj = ProcessList.HIDDEN_APP_MAX_ADJ; - hiddenAdj = clientHiddenAdj = emptyAdj = ProcessList.HIDDEN_APP_MIN_ADJ; + pkgList.put(_info.packageName, null); + maxAdj = ProcessList.UNKNOWN_ADJ; curRawAdj = setRawAdj = -100; curAdj = setAdj = -100; persistent = false; removed = false; + lastStateTime = lastPssTime = nextPssTime = SystemClock.uptimeMillis(); } public void setPid(int _pid) { @@ -347,7 +383,53 @@ class ProcessRecord { shortStringName = null; stringName = null; } - + + public void makeActive(IApplicationThread _thread, ProcessStatsService tracker) { + if (thread == null) { + final ProcessStats.ProcessState origBase = baseProcessTracker; + if (origBase != null) { + origBase.setState(ProcessStats.STATE_NOTHING, + tracker.getMemFactorLocked(), SystemClock.uptimeMillis(), pkgList); + origBase.makeInactive(); + } + baseProcessTracker = tracker.getProcessStateLocked(info.packageName, info.uid, + processName); + baseProcessTracker.makeActive(); + for (int i=0; i<pkgList.size(); i++) { + ProcessStats.ProcessState ps = pkgList.valueAt(i); + if (ps != null && ps != origBase) { + ps.makeInactive(); + } + ps = tracker.getProcessStateLocked(pkgList.keyAt(i), info.uid, processName); + if (ps != baseProcessTracker) { + ps.makeActive(); + } + pkgList.setValueAt(i, ps); + } + } + thread = _thread; + } + + public void makeInactive(ProcessStatsService tracker) { + thread = null; + final ProcessStats.ProcessState origBase = baseProcessTracker; + if (origBase != null) { + if (origBase != null) { + origBase.setState(ProcessStats.STATE_NOTHING, + tracker.getMemFactorLocked(), SystemClock.uptimeMillis(), pkgList); + origBase.makeInactive(); + } + baseProcessTracker = null; + for (int i=0; i<pkgList.size(); i++) { + ProcessStats.ProcessState ps = pkgList.valueAt(i); + if (ps != null && ps != origBase) { + ps.makeInactive(); + } + pkgList.setValueAt(i, null); + } + } + } + /** * This method returns true if any of the activities within the process record are interesting * to the user. See HistoryRecord.isInterestingToUserLocked() @@ -380,16 +462,37 @@ class ProcessRecord { void updateHasAboveClientLocked() { hasAboveClient = false; - if (connections.size() > 0) { - for (ConnectionRecord cr : connections) { - if ((cr.flags&Context.BIND_ABOVE_CLIENT) != 0) { - hasAboveClient = true; - break; - } + for (int i=connections.size()-1; i>=0; i--) { + ConnectionRecord cr = connections.valueAt(i); + if ((cr.flags&Context.BIND_ABOVE_CLIENT) != 0) { + hasAboveClient = true; + break; } } } + int modifyRawOomAdj(int adj) { + if (hasAboveClient) { + // If this process has bound to any services with BIND_ABOVE_CLIENT, + // then we need to drop its adjustment to be lower than the service's + // in order to honor the request. We want to drop it by one adjustment + // level... but there is special meaning applied to various levels so + // we will skip some of them. + if (adj < ProcessList.FOREGROUND_APP_ADJ) { + // System process will not get dropped, ever + } else if (adj < ProcessList.VISIBLE_APP_ADJ) { + adj = ProcessList.VISIBLE_APP_ADJ; + } else if (adj < ProcessList.PERCEPTIBLE_APP_ADJ) { + adj = ProcessList.PERCEPTIBLE_APP_ADJ; + } else if (adj < ProcessList.CACHED_APP_MIN_ADJ) { + adj = ProcessList.CACHED_APP_MIN_ADJ; + } else if (adj < ProcessList.CACHED_APP_MAX_ADJ) { + adj++; + } + } + return adj; + } + public String toShortString() { if (shortStringName != null) { return shortStringName; @@ -409,8 +512,14 @@ class ProcessRecord { } else { sb.append('u'); sb.append(userId); - sb.append('a'); - sb.append(UserHandle.getAppId(info.uid)); + int appId = UserHandle.getAppId(info.uid); + if (appId >= Process.FIRST_APPLICATION_UID) { + sb.append('a'); + sb.append(appId - Process.FIRST_APPLICATION_UID); + } else { + sb.append('s'); + sb.append(appId); + } if (uid != info.uid) { sb.append('i'); sb.append(UserHandle.getAppId(uid) - Process.FIRST_ISOLATED_UID); @@ -430,24 +539,97 @@ class ProcessRecord { sb.append('}'); return stringName = sb.toString(); } - + + public String makeAdjReason() { + if (adjSource != null || adjTarget != null) { + StringBuilder sb = new StringBuilder(128); + sb.append(' '); + if (adjTarget instanceof ComponentName) { + sb.append(((ComponentName)adjTarget).flattenToShortString()); + } else if (adjTarget != null) { + sb.append(adjTarget.toString()); + } else { + sb.append("{null}"); + } + sb.append("<="); + if (adjSource instanceof ProcessRecord) { + sb.append("Proc{"); + sb.append(((ProcessRecord)adjSource).toShortString()); + sb.append("}"); + } else if (adjSource != null) { + sb.append(adjSource.toString()); + } else { + sb.append("{null}"); + } + return sb.toString(); + } + return null; + } + /* * Return true if package has been added false if not */ - public boolean addPackage(String pkg) { - if (!pkgList.contains(pkg)) { - pkgList.add(pkg); + public boolean addPackage(String pkg, ProcessStatsService tracker) { + if (!pkgList.containsKey(pkg)) { + if (baseProcessTracker != null) { + ProcessStats.ProcessState state = tracker.getProcessStateLocked( + pkg, info.uid, processName); + pkgList.put(pkg, state); + if (state != baseProcessTracker) { + state.makeActive(); + } + } else { + pkgList.put(pkg, null); + } return true; } return false; } - + + public int getSetAdjWithServices() { + if (setAdj >= ProcessList.CACHED_APP_MIN_ADJ) { + if (hasStartedServices) { + return ProcessList.SERVICE_B_ADJ; + } + } + return setAdj; + } + + public void forceProcessStateUpTo(int newState) { + if (repProcState > newState) { + curProcState = repProcState = newState; + } + } + /* * Delete all packages from list except the package indicated in info */ - public void resetPackageList() { - pkgList.clear(); - pkgList.add(info.packageName); + public void resetPackageList(ProcessStatsService tracker) { + final int N = pkgList.size(); + if (baseProcessTracker != null) { + long now = SystemClock.uptimeMillis(); + baseProcessTracker.setState(ProcessStats.STATE_NOTHING, + tracker.getMemFactorLocked(), now, pkgList); + if (N != 1) { + for (int i=0; i<N; i++) { + ProcessStats.ProcessState ps = pkgList.valueAt(i); + if (ps != null && ps != baseProcessTracker) { + ps.makeInactive(); + } + + } + pkgList.clear(); + ProcessStats.ProcessState ps = tracker.getProcessStateLocked( + info.packageName, info.uid, processName); + pkgList.put(info.packageName, ps); + if (ps != baseProcessTracker) { + ps.makeActive(); + } + } + } else if (N != 1) { + pkgList.clear(); + pkgList.put(info.packageName, null); + } } public String[] getPackageList() { @@ -456,7 +638,9 @@ class ProcessRecord { return null; } String list[] = new String[size]; - pkgList.toArray(list); + for (int i=0; i<pkgList.size(); i++) { + list[i] = pkgList.keyAt(i); + } return list; } } diff --git a/services/java/com/android/server/am/ProcessStatsService.java b/services/java/com/android/server/am/ProcessStatsService.java new file mode 100644 index 000000000000..50a7b5c5e56c --- /dev/null +++ b/services/java/com/android/server/am/ProcessStatsService.java @@ -0,0 +1,899 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.am; + +import android.app.AppGlobals; +import android.content.pm.IPackageManager; +import android.content.pm.PackageManager; +import android.os.Binder; +import android.os.Parcel; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.util.ArrayMap; +import android.util.AtomicFile; +import android.util.Slog; +import android.util.SparseArray; +import android.util.TimeUtils; +import com.android.internal.app.IProcessStats; +import com.android.internal.app.ProcessStats; +import com.android.internal.os.BackgroundThread; + +import java.io.File; +import java.io.FileDescriptor; +import java.io.FileInputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.locks.ReentrantLock; + +public final class ProcessStatsService extends IProcessStats.Stub { + static final String TAG = "ProcessStatsService"; + static final boolean DEBUG = false; + + // Most data is kept in a sparse data structure: an integer array which integer + // holds the type of the entry, and the identifier for a long array that data + // exists in and the offset into the array to find it. The constants below + // define the encoding of that data in an integer. + + static final int MAX_HISTORIC_STATES = 8; // Maximum number of historic states we will keep. + static final String STATE_FILE_PREFIX = "state-"; // Prefix to use for state filenames. + static final String STATE_FILE_SUFFIX = ".bin"; // Suffix to use for state filenames. + static final String STATE_FILE_CHECKIN_SUFFIX = ".ci"; // State files that have checked in. + static long WRITE_PERIOD = 30*60*1000; // Write file every 30 minutes or so. + + final ActivityManagerService mAm; + final File mBaseDir; + ProcessStats mProcessStats; + AtomicFile mFile; + boolean mCommitPending; + boolean mShuttingDown; + int mLastMemOnlyState = -1; + boolean mMemFactorLowered; + + final ReentrantLock mWriteLock = new ReentrantLock(); + final Object mPendingWriteLock = new Object(); + AtomicFile mPendingWriteFile; + Parcel mPendingWrite; + boolean mPendingWriteCommitted; + long mLastWriteTime; + + public ProcessStatsService(ActivityManagerService am, File file) { + mAm = am; + mBaseDir = file; + mBaseDir.mkdirs(); + mProcessStats = new ProcessStats(true); + updateFile(); + SystemProperties.addChangeCallback(new Runnable() { + @Override public void run() { + synchronized (mAm) { + if (mProcessStats.evaluateSystemProperties(false)) { + mProcessStats.mFlags |= ProcessStats.FLAG_SYSPROPS; + writeStateLocked(true, true); + mProcessStats.evaluateSystemProperties(true); + } + } + } + }); + } + + @Override + public boolean onTransact(int code, Parcel data, Parcel reply, int flags) + throws RemoteException { + try { + return super.onTransact(code, data, reply, flags); + } catch (RuntimeException e) { + if (!(e instanceof SecurityException)) { + Slog.wtf(TAG, "Process Stats Crash", e); + } + throw e; + } + } + + public ProcessStats.ProcessState getProcessStateLocked(String packageName, + int uid, String processName) { + return mProcessStats.getProcessStateLocked(packageName, uid, processName); + } + + public ProcessStats.ServiceState getServiceStateLocked(String packageName, int uid, + String processName, String className) { + return mProcessStats.getServiceStateLocked(packageName, uid, processName, className); + } + + public boolean isMemFactorLowered() { + return mMemFactorLowered; + } + + public boolean setMemFactorLocked(int memFactor, boolean screenOn, long now) { + mMemFactorLowered = memFactor < mLastMemOnlyState; + mLastMemOnlyState = memFactor; + if (screenOn) { + memFactor += ProcessStats.ADJ_SCREEN_ON; + } + if (memFactor != mProcessStats.mMemFactor) { + if (mProcessStats.mMemFactor != ProcessStats.STATE_NOTHING) { + mProcessStats.mMemFactorDurations[mProcessStats.mMemFactor] + += now - mProcessStats.mStartTime; + } + mProcessStats.mMemFactor = memFactor; + mProcessStats.mStartTime = now; + ArrayMap<String, SparseArray<ProcessStats.PackageState>> pmap + = mProcessStats.mPackages.getMap(); + for (int i=0; i<pmap.size(); i++) { + SparseArray<ProcessStats.PackageState> uids = pmap.valueAt(i); + for (int j=0; j<uids.size(); j++) { + ProcessStats.PackageState pkg = uids.valueAt(j); + ArrayMap<String, ProcessStats.ServiceState> services = pkg.mServices; + for (int k=0; k<services.size(); k++) { + ProcessStats.ServiceState service = services.valueAt(k); + if (service.isInUse()) { + if (service.mStartedState != ProcessStats.STATE_NOTHING) { + service.setStarted(true, memFactor, now); + } + if (service.mBoundState != ProcessStats.STATE_NOTHING) { + service.setBound(true, memFactor, now); + } + if (service.mExecState != ProcessStats.STATE_NOTHING) { + service.setExecuting(true, memFactor, now); + } + } + } + } + } + return true; + } + return false; + } + + public int getMemFactorLocked() { + return mProcessStats.mMemFactor != ProcessStats.STATE_NOTHING ? mProcessStats.mMemFactor : 0; + } + + public boolean shouldWriteNowLocked(long now) { + if (now > (mLastWriteTime+WRITE_PERIOD)) { + if (SystemClock.elapsedRealtime() + > (mProcessStats.mTimePeriodStartRealtime+ProcessStats.COMMIT_PERIOD)) { + mCommitPending = true; + } + return true; + } + return false; + } + + public void shutdownLocked() { + Slog.w(TAG, "Writing process stats before shutdown..."); + mProcessStats.mFlags |= ProcessStats.FLAG_SHUTDOWN; + writeStateSyncLocked(); + mShuttingDown = true; + } + + public void writeStateAsyncLocked() { + writeStateLocked(false); + } + + public void writeStateSyncLocked() { + writeStateLocked(true); + } + + private void writeStateLocked(boolean sync) { + if (mShuttingDown) { + return; + } + boolean commitPending = mCommitPending; + mCommitPending = false; + writeStateLocked(sync, commitPending); + } + + public void writeStateLocked(boolean sync, final boolean commit) { + synchronized (mPendingWriteLock) { + long now = SystemClock.uptimeMillis(); + if (mPendingWrite == null || !mPendingWriteCommitted) { + mPendingWrite = Parcel.obtain(); + mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime(); + if (commit) { + mProcessStats.mFlags |= ProcessStats.FLAG_COMPLETE; + } + mProcessStats.writeToParcel(mPendingWrite, 0); + mPendingWriteFile = new AtomicFile(mFile.getBaseFile()); + mPendingWriteCommitted = commit; + } + if (commit) { + mProcessStats.resetSafely(); + updateFile(); + } + mLastWriteTime = SystemClock.uptimeMillis(); + Slog.i(TAG, "Prepared write state in " + (SystemClock.uptimeMillis()-now) + "ms"); + if (!sync) { + BackgroundThread.getHandler().post(new Runnable() { + @Override public void run() { + performWriteState(); + } + }); + return; + } + } + + performWriteState(); + } + + private void updateFile() { + mFile = new AtomicFile(new File(mBaseDir, STATE_FILE_PREFIX + + mProcessStats.mTimePeriodStartClockStr + STATE_FILE_SUFFIX)); + mLastWriteTime = SystemClock.uptimeMillis(); + } + + void performWriteState() { + if (DEBUG) Slog.d(TAG, "Performing write to " + mFile.getBaseFile()); + Parcel data; + AtomicFile file; + synchronized (mPendingWriteLock) { + data = mPendingWrite; + file = mPendingWriteFile; + mPendingWriteCommitted = false; + if (data == null) { + return; + } + mPendingWrite = null; + mPendingWriteFile = null; + mWriteLock.lock(); + } + + FileOutputStream stream = null; + try { + stream = file.startWrite(); + stream.write(data.marshall()); + stream.flush(); + file.finishWrite(stream); + if (DEBUG) Slog.d(TAG, "Write completed successfully!"); + } catch (IOException e) { + Slog.w(TAG, "Error writing process statistics", e); + file.failWrite(stream); + } finally { + data.recycle(); + trimHistoricStatesWriteLocked(); + mWriteLock.unlock(); + } + } + + boolean readLocked(ProcessStats stats, AtomicFile file) { + try { + FileInputStream stream = file.openRead(); + stats.read(stream); + stream.close(); + if (stats.mReadError != null) { + Slog.w(TAG, "Ignoring existing stats; " + stats.mReadError); + if (DEBUG) { + ArrayMap<String, SparseArray<ProcessStats.ProcessState>> procMap + = stats.mProcesses.getMap(); + final int NPROC = procMap.size(); + for (int ip=0; ip<NPROC; ip++) { + Slog.w(TAG, "Process: " + procMap.keyAt(ip)); + SparseArray<ProcessStats.ProcessState> uids = procMap.valueAt(ip); + final int NUID = uids.size(); + for (int iu=0; iu<NUID; iu++) { + Slog.w(TAG, " Uid " + uids.keyAt(iu) + ": " + uids.valueAt(iu)); + } + } + ArrayMap<String, SparseArray<ProcessStats.PackageState>> pkgMap + = stats.mPackages.getMap(); + final int NPKG = pkgMap.size(); + for (int ip=0; ip<NPKG; ip++) { + Slog.w(TAG, "Package: " + pkgMap.keyAt(ip)); + SparseArray<ProcessStats.PackageState> uids = pkgMap.valueAt(ip); + final int NUID = uids.size(); + for (int iu=0; iu<NUID; iu++) { + Slog.w(TAG, " Uid: " + uids.keyAt(iu)); + ProcessStats.PackageState pkgState = uids.valueAt(iu); + final int NPROCS = pkgState.mProcesses.size(); + for (int iproc=0; iproc<NPROCS; iproc++) { + Slog.w(TAG, " Process " + pkgState.mProcesses.keyAt(iproc) + + ": " + pkgState.mProcesses.valueAt(iproc)); + } + final int NSRVS = pkgState.mServices.size(); + for (int isvc=0; isvc<NSRVS; isvc++) { + Slog.w(TAG, " Service " + pkgState.mServices.keyAt(isvc) + + ": " + pkgState.mServices.valueAt(isvc)); + } + } + } + } + return false; + } + } catch (Throwable e) { + stats.mReadError = "caught exception: " + e; + Slog.e(TAG, "Error reading process statistics", e); + return false; + } + return true; + } + + private ArrayList<String> getCommittedFiles(int minNum, boolean inclCurrent, + boolean inclCheckedIn) { + File[] files = mBaseDir.listFiles(); + if (files == null || files.length <= minNum) { + return null; + } + ArrayList<String> filesArray = new ArrayList<String>(files.length); + String currentFile = mFile.getBaseFile().getPath(); + if (DEBUG) Slog.d(TAG, "Collecting " + files.length + " files except: " + currentFile); + for (int i=0; i<files.length; i++) { + File file = files[i]; + String fileStr = file.getPath(); + if (DEBUG) Slog.d(TAG, "Collecting: " + fileStr); + if (!inclCheckedIn && fileStr.endsWith(STATE_FILE_CHECKIN_SUFFIX)) { + if (DEBUG) Slog.d(TAG, "Skipping: already checked in"); + continue; + } + if (!inclCurrent && fileStr.equals(currentFile)) { + if (DEBUG) Slog.d(TAG, "Skipping: current stats"); + continue; + } + filesArray.add(fileStr); + } + Collections.sort(filesArray); + return filesArray; + } + + public void trimHistoricStatesWriteLocked() { + ArrayList<String> filesArray = getCommittedFiles(MAX_HISTORIC_STATES, false, true); + if (filesArray == null) { + return; + } + while (filesArray.size() > MAX_HISTORIC_STATES) { + String file = filesArray.remove(0); + Slog.i(TAG, "Pruning old procstats: " + file); + (new File(file)).delete(); + } + } + + boolean dumpFilteredProcessesCsvLocked(PrintWriter pw, String header, + boolean sepScreenStates, int[] screenStates, boolean sepMemStates, int[] memStates, + boolean sepProcStates, int[] procStates, long now, String reqPackage) { + ArrayList<ProcessStats.ProcessState> procs = mProcessStats.collectProcessesLocked( + screenStates, memStates, procStates, procStates, now, reqPackage, false); + if (procs.size() > 0) { + if (header != null) { + pw.println(header); + } + ProcessStats.dumpProcessListCsv(pw, procs, sepScreenStates, screenStates, + sepMemStates, memStates, sepProcStates, procStates, now); + return true; + } + return false; + } + + static int[] parseStateList(String[] states, int mult, String arg, boolean[] outSep, + String[] outError) { + ArrayList<Integer> res = new ArrayList<Integer>(); + int lastPos = 0; + for (int i=0; i<=arg.length(); i++) { + char c = i < arg.length() ? arg.charAt(i) : 0; + if (c != ',' && c != '+' && c != ' ' && c != 0) { + continue; + } + boolean isSep = c == ','; + if (lastPos == 0) { + // We now know the type of op. + outSep[0] = isSep; + } else if (c != 0 && outSep[0] != isSep) { + outError[0] = "inconsistent separators (can't mix ',' with '+')"; + return null; + } + if (lastPos < (i-1)) { + String str = arg.substring(lastPos, i); + for (int j=0; j<states.length; j++) { + if (str.equals(states[j])) { + res.add(j); + str = null; + break; + } + } + if (str != null) { + outError[0] = "invalid word \"" + str + "\""; + return null; + } + } + lastPos = i + 1; + } + + int[] finalRes = new int[res.size()]; + for (int i=0; i<res.size(); i++) { + finalRes[i] = res.get(i) * mult; + } + return finalRes; + } + + public byte[] getCurrentStats(List<ParcelFileDescriptor> historic) { + mAm.mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.PACKAGE_USAGE_STATS, null); + Parcel current = Parcel.obtain(); + mWriteLock.lock(); + try { + synchronized (mAm) { + mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime(); + mProcessStats.writeToParcel(current, 0); + } + if (historic != null) { + ArrayList<String> files = getCommittedFiles(0, false, true); + if (files != null) { + for (int i=files.size()-1; i>=0; i--) { + try { + ParcelFileDescriptor pfd = ParcelFileDescriptor.open( + new File(files.get(i)), ParcelFileDescriptor.MODE_READ_ONLY); + historic.add(pfd); + } catch (IOException e) { + Slog.w(TAG, "Failure opening procstat file " + files.get(i), e); + } + } + } + } + } finally { + mWriteLock.unlock(); + } + return current.marshall(); + } + + public ParcelFileDescriptor getStatsOverTime(long minTime) { + mAm.mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.PACKAGE_USAGE_STATS, null); + mWriteLock.lock(); + try { + Parcel current = Parcel.obtain(); + long curTime; + synchronized (mAm) { + mProcessStats.mTimePeriodEndRealtime = SystemClock.elapsedRealtime(); + mProcessStats.writeToParcel(current, 0); + curTime = mProcessStats.mTimePeriodEndRealtime + - mProcessStats.mTimePeriodStartRealtime; + } + if (curTime < minTime) { + // Need to add in older stats to reach desired time. + ArrayList<String> files = getCommittedFiles(0, false, true); + if (files != null && files.size() > 0) { + current.setDataPosition(0); + ProcessStats stats = ProcessStats.CREATOR.createFromParcel(current); + current.recycle(); + int i = files.size()-1; + while (i >= 0 && (stats.mTimePeriodEndRealtime + - stats.mTimePeriodStartRealtime) < minTime) { + AtomicFile file = new AtomicFile(new File(files.get(i))); + i--; + ProcessStats moreStats = new ProcessStats(false); + readLocked(moreStats, file); + if (moreStats.mReadError == null) { + stats.add(moreStats); + StringBuilder sb = new StringBuilder(); + sb.append("Added stats: "); + sb.append(moreStats.mTimePeriodStartClockStr); + sb.append(", over "); + TimeUtils.formatDuration(moreStats.mTimePeriodEndRealtime + - moreStats.mTimePeriodStartRealtime, sb); + Slog.i(TAG, sb.toString()); + } else { + Slog.w(TAG, "Failure reading " + files.get(i+1) + "; " + + moreStats.mReadError); + continue; + } + } + current = Parcel.obtain(); + stats.writeToParcel(current, 0); + } + } + final byte[] outData = current.marshall(); + current.recycle(); + final ParcelFileDescriptor[] fds = ParcelFileDescriptor.createPipe(); + Thread thr = new Thread("ProcessStats pipe output") { + public void run() { + FileOutputStream fout = new ParcelFileDescriptor.AutoCloseOutputStream(fds[1]); + try { + fout.write(outData); + fout.close(); + } catch (IOException e) { + Slog.w(TAG, "Failure writing pipe", e); + } + } + }; + thr.start(); + return fds[0]; + } catch (IOException e) { + Slog.w(TAG, "Failed building output pipe", e); + } finally { + mWriteLock.unlock(); + } + return null; + } + + public int getCurrentMemoryState() { + synchronized (mAm) { + return mLastMemOnlyState; + } + } + + static private void dumpHelp(PrintWriter pw) { + pw.println("Process stats (procstats) dump options:"); + pw.println(" [--checkin|-c|--csv] [--csv-screen] [--csv-proc] [--csv-mem]"); + pw.println(" [--details] [--full-details] [--current] [--hours] [--active]"); + pw.println(" [--commit] [--reset] [--clear] [--write] [-h] [<package.name>]"); + pw.println(" --checkin: perform a checkin: print and delete old committed states."); + pw.println(" --c: print only state in checkin format."); + pw.println(" --csv: output data suitable for putting in a spreadsheet."); + pw.println(" --csv-screen: on, off."); + pw.println(" --csv-mem: norm, mod, low, crit."); + pw.println(" --csv-proc: pers, top, fore, vis, precept, backup,"); + pw.println(" service, home, prev, cached"); + pw.println(" --details: dump per-package details, not just summary."); + pw.println(" --full-details: dump all timing and active state details."); + pw.println(" --current: only dump current state."); + pw.println(" --hours: aggregate over about N last hours."); + pw.println(" --active: only show currently active processes/services."); + pw.println(" --commit: commit current stats to disk and reset to start new stats."); + pw.println(" --reset: reset current stats, without committing."); + pw.println(" --clear: clear all stats; does both --reset and deletes old stats."); + pw.println(" --write: write current in-memory stats to disk."); + pw.println(" --read: replace current stats with last-written stats."); + pw.println(" -a: print everything."); + pw.println(" -h: print this help text."); + pw.println(" <package.name>: optional name of package to filter output by."); + } + + @Override + protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mAm.checkCallingPermission(android.Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump procstats from from pid=" + + Binder.getCallingPid() + ", uid=" + Binder.getCallingUid() + + " without permission " + android.Manifest.permission.DUMP); + return; + } + + long ident = Binder.clearCallingIdentity(); + try { + dumpInner(fd, pw, args); + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + private void dumpInner(FileDescriptor fd, PrintWriter pw, String[] args) { + final long now = SystemClock.uptimeMillis(); + + boolean isCheckin = false; + boolean isCompact = false; + boolean isCsv = false; + boolean currentOnly = false; + boolean dumpDetails = false; + boolean dumpFullDetails = false; + boolean dumpAll = false; + int aggregateHours = 0; + boolean activeOnly = false; + String reqPackage = null; + boolean csvSepScreenStats = false; + int[] csvScreenStats = new int[] { ProcessStats.ADJ_SCREEN_OFF, ProcessStats.ADJ_SCREEN_ON}; + boolean csvSepMemStats = false; + int[] csvMemStats = new int[] { ProcessStats.ADJ_MEM_FACTOR_CRITICAL}; + boolean csvSepProcStats = true; + int[] csvProcStats = ProcessStats.ALL_PROC_STATES; + if (args != null) { + for (int i=0; i<args.length; i++) { + String arg = args[i]; + if ("--checkin".equals(arg)) { + isCheckin = true; + } else if ("-c".equals(arg)) { + isCompact = true; + } else if ("--csv".equals(arg)) { + isCsv = true; + } else if ("--csv-screen".equals(arg)) { + i++; + if (i >= args.length) { + pw.println("Error: argument required for --csv-screen"); + dumpHelp(pw); + return; + } + boolean[] sep = new boolean[1]; + String[] error = new String[1]; + csvScreenStats = parseStateList(ProcessStats.ADJ_SCREEN_NAMES_CSV, ProcessStats.ADJ_SCREEN_MOD, + args[i], sep, error); + if (csvScreenStats == null) { + pw.println("Error in \"" + args[i] + "\": " + error[0]); + dumpHelp(pw); + return; + } + csvSepScreenStats = sep[0]; + } else if ("--csv-mem".equals(arg)) { + i++; + if (i >= args.length) { + pw.println("Error: argument required for --csv-mem"); + dumpHelp(pw); + return; + } + boolean[] sep = new boolean[1]; + String[] error = new String[1]; + csvMemStats = parseStateList(ProcessStats.ADJ_MEM_NAMES_CSV, 1, args[i], sep, error); + if (csvMemStats == null) { + pw.println("Error in \"" + args[i] + "\": " + error[0]); + dumpHelp(pw); + return; + } + csvSepMemStats = sep[0]; + } else if ("--csv-proc".equals(arg)) { + i++; + if (i >= args.length) { + pw.println("Error: argument required for --csv-proc"); + dumpHelp(pw); + return; + } + boolean[] sep = new boolean[1]; + String[] error = new String[1]; + csvProcStats = parseStateList(ProcessStats.STATE_NAMES_CSV, 1, args[i], sep, error); + if (csvProcStats == null) { + pw.println("Error in \"" + args[i] + "\": " + error[0]); + dumpHelp(pw); + return; + } + csvSepProcStats = sep[0]; + } else if ("--details".equals(arg)) { + dumpDetails = true; + } else if ("--full-details".equals(arg)) { + dumpFullDetails = true; + } else if ("--hours".equals(arg)) { + i++; + if (i >= args.length) { + pw.println("Error: argument required for --hours"); + dumpHelp(pw); + return; + } + try { + aggregateHours = Integer.parseInt(args[i]); + } catch (NumberFormatException e) { + pw.println("Error: --hours argument not an int -- " + args[i]); + dumpHelp(pw); + return; + } + } else if ("--active".equals(arg)) { + activeOnly = true; + currentOnly = true; + } else if ("--current".equals(arg)) { + currentOnly = true; + } else if ("--commit".equals(arg)) { + synchronized (mAm) { + mProcessStats.mFlags |= ProcessStats.FLAG_COMPLETE; + writeStateLocked(true, true); + pw.println("Process stats committed."); + } + return; + } else if ("--reset".equals(arg)) { + synchronized (mAm) { + mProcessStats.resetSafely(); + pw.println("Process stats reset."); + } + return; + } else if ("--clear".equals(arg)) { + synchronized (mAm) { + mProcessStats.resetSafely(); + ArrayList<String> files = getCommittedFiles(0, true, true); + if (files != null) { + for (int fi=0; fi<files.size(); fi++) { + (new File(files.get(fi))).delete(); + } + } + pw.println("All process stats cleared."); + } + return; + } else if ("--write".equals(arg)) { + synchronized (mAm) { + writeStateSyncLocked(); + pw.println("Process stats written."); + } + return; + } else if ("--read".equals(arg)) { + synchronized (mAm) { + readLocked(mProcessStats, mFile); + pw.println("Process stats read."); + } + return; + } else if ("-h".equals(arg)) { + dumpHelp(pw); + return; + } else if ("-a".equals(arg)) { + dumpDetails = true; + dumpAll = true; + } else if (arg.length() > 0 && arg.charAt(0) == '-'){ + pw.println("Unknown option: " + arg); + dumpHelp(pw); + return; + } else { + // Not an option, last argument must be a package name. + try { + IPackageManager pm = AppGlobals.getPackageManager(); + if (pm.getPackageUid(arg, UserHandle.getCallingUserId()) >= 0) { + reqPackage = arg; + // Include all details, since we know we are only going to + // be dumping a smaller set of data. In fact only the details + // container per-package data, so that are needed to be able + // to dump anything at all when filtering by package. + dumpDetails = true; + } + } catch (RemoteException e) { + } + if (reqPackage == null) { + pw.println("Unknown package: " + arg); + dumpHelp(pw); + return; + } + } + } + } + + if (isCsv) { + pw.print("Processes running summed over"); + if (!csvSepScreenStats) { + for (int i=0; i<csvScreenStats.length; i++) { + pw.print(" "); + ProcessStats.printScreenLabelCsv(pw, csvScreenStats[i]); + } + } + if (!csvSepMemStats) { + for (int i=0; i<csvMemStats.length; i++) { + pw.print(" "); + ProcessStats.printMemLabelCsv(pw, csvMemStats[i]); + } + } + if (!csvSepProcStats) { + for (int i=0; i<csvProcStats.length; i++) { + pw.print(" "); + pw.print(ProcessStats.STATE_NAMES_CSV[csvProcStats[i]]); + } + } + pw.println(); + synchronized (mAm) { + dumpFilteredProcessesCsvLocked(pw, null, + csvSepScreenStats, csvScreenStats, csvSepMemStats, csvMemStats, + csvSepProcStats, csvProcStats, now, reqPackage); + /* + dumpFilteredProcessesCsvLocked(pw, "Processes running while critical mem:", + false, new int[] {ADJ_SCREEN_OFF, ADJ_SCREEN_ON}, + true, new int[] {ADJ_MEM_FACTOR_CRITICAL}, + true, new int[] {STATE_PERSISTENT, STATE_TOP, STATE_FOREGROUND, STATE_VISIBLE, + STATE_PERCEPTIBLE, STATE_BACKUP, STATE_SERVICE, STATE_HOME, + STATE_PREVIOUS, STATE_CACHED}, + now, reqPackage); + dumpFilteredProcessesCsvLocked(pw, "Processes running over all mem:", + false, new int[] {ADJ_SCREEN_OFF, ADJ_SCREEN_ON}, + false, new int[] {ADJ_MEM_FACTOR_CRITICAL, ADJ_MEM_FACTOR_LOW, + ADJ_MEM_FACTOR_MODERATE, ADJ_MEM_FACTOR_MODERATE}, + true, new int[] {STATE_PERSISTENT, STATE_TOP, STATE_FOREGROUND, STATE_VISIBLE, + STATE_PERCEPTIBLE, STATE_BACKUP, STATE_SERVICE, STATE_HOME, + STATE_PREVIOUS, STATE_CACHED}, + now, reqPackage); + */ + } + return; + } else if (aggregateHours != 0) { + ParcelFileDescriptor pfd = getStatsOverTime(aggregateHours*60*60*1000 + - (ProcessStats.COMMIT_PERIOD/2)); + if (pfd == null) { + pw.println("Unable to build stats!"); + return; + } + ProcessStats stats = new ProcessStats(false); + InputStream stream = new ParcelFileDescriptor.AutoCloseInputStream(pfd); + stats.read(stream); + if (stats.mReadError != null) { + pw.print("Failure reading: "); pw.println(stats.mReadError); + return; + } + if (isCompact) { + stats.dumpCheckinLocked(pw, reqPackage); + } else { + if (dumpDetails || dumpFullDetails) { + stats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll, activeOnly); + } else { + stats.dumpSummaryLocked(pw, reqPackage, now, activeOnly); + } + } + return; + } + + boolean sepNeeded = false; + if (!currentOnly || isCheckin) { + mWriteLock.lock(); + try { + ArrayList<String> files = getCommittedFiles(0, false, !isCheckin); + if (files != null) { + for (int i=0; i<files.size(); i++) { + if (DEBUG) Slog.d(TAG, "Retrieving state: " + files.get(i)); + try { + AtomicFile file = new AtomicFile(new File(files.get(i))); + ProcessStats processStats = new ProcessStats(false); + readLocked(processStats, file); + if (processStats.mReadError != null) { + if (isCheckin || isCompact) pw.print("err,"); + pw.print("Failure reading "); pw.print(files.get(i)); + pw.print("; "); pw.println(processStats.mReadError); + if (DEBUG) Slog.d(TAG, "Deleting state: " + files.get(i)); + (new File(files.get(i))).delete(); + continue; + } + String fileStr = file.getBaseFile().getPath(); + boolean checkedIn = fileStr.endsWith(STATE_FILE_CHECKIN_SUFFIX); + if (isCheckin || isCompact) { + // Don't really need to lock because we uniquely own this object. + processStats.dumpCheckinLocked(pw, reqPackage); + } else { + if (sepNeeded) { + pw.println(); + } else { + sepNeeded = true; + } + pw.print("COMMITTED STATS FROM "); + pw.print(processStats.mTimePeriodStartClockStr); + if (checkedIn) pw.print(" (checked in)"); + pw.println(":"); + // Don't really need to lock because we uniquely own this object. + // Always dump summary here, dumping all details is just too + // much crud. + if (dumpFullDetails) { + mProcessStats.dumpLocked(pw, reqPackage, now, false, false, + activeOnly); + } else { + processStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly); + } + } + if (isCheckin) { + // Rename file suffix to mark that it has checked in. + file.getBaseFile().renameTo(new File( + fileStr + STATE_FILE_CHECKIN_SUFFIX)); + } + } catch (Throwable e) { + pw.print("**** FAILURE DUMPING STATE: "); pw.println(files.get(i)); + e.printStackTrace(pw); + } + } + } + } finally { + mWriteLock.unlock(); + } + } + if (!isCheckin) { + synchronized (mAm) { + if (isCompact) { + mProcessStats.dumpCheckinLocked(pw, reqPackage); + } else { + if (sepNeeded) { + pw.println(); + pw.println("CURRENT STATS:"); + } + if (dumpDetails || dumpFullDetails) { + mProcessStats.dumpLocked(pw, reqPackage, now, !dumpFullDetails, dumpAll, + activeOnly); + if (dumpAll) { + pw.print(" mFile="); pw.println(mFile.getBaseFile()); + } + } else { + mProcessStats.dumpSummaryLocked(pw, reqPackage, now, activeOnly); + } + } + } + } + } +} diff --git a/services/java/com/android/server/am/ProviderMap.java b/services/java/com/android/server/am/ProviderMap.java index 9dbf5f51cbd7..7da8c48dea35 100644 --- a/services/java/com/android/server/am/ProviderMap.java +++ b/services/java/com/android/server/am/ProviderMap.java @@ -22,6 +22,7 @@ import android.os.RemoteException; import android.os.UserHandle; import android.util.Slog; import android.util.SparseArray; +import com.android.internal.os.TransferPipe; import java.io.FileDescriptor; import java.io.IOException; @@ -35,7 +36,7 @@ import java.util.Map; * Keeps track of content providers by authority (name) and class. It separates the mapping by * user and ones that are not user-specific (system providers). */ -public class ProviderMap { +public final class ProviderMap { private static final String TAG = "ProviderMap"; @@ -230,58 +231,87 @@ public class ProviderMap { return didSomething; } - private void dumpProvidersByClassLocked(PrintWriter pw, boolean dumpAll, - HashMap<ComponentName, ContentProviderRecord> map) { + private boolean dumpProvidersByClassLocked(PrintWriter pw, boolean dumpAll, String dumpPackage, + String header, boolean needSep, HashMap<ComponentName, ContentProviderRecord> map) { Iterator<Map.Entry<ComponentName, ContentProviderRecord>> it = map.entrySet().iterator(); + boolean written = false; while (it.hasNext()) { Map.Entry<ComponentName, ContentProviderRecord> e = it.next(); ContentProviderRecord r = e.getValue(); + if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { + continue; + } + if (needSep) { + pw.println(""); + needSep = false; + } + if (header != null) { + pw.println(header); + header = null; + } + written = true; pw.print(" * "); pw.println(r); r.dump(pw, " ", dumpAll); } + return written; } - private void dumpProvidersByNameLocked(PrintWriter pw, - HashMap<String, ContentProviderRecord> map) { + private boolean dumpProvidersByNameLocked(PrintWriter pw, String dumpPackage, + String header, boolean needSep, HashMap<String, ContentProviderRecord> map) { Iterator<Map.Entry<String, ContentProviderRecord>> it = map.entrySet().iterator(); + boolean written = false; while (it.hasNext()) { Map.Entry<String, ContentProviderRecord> e = it.next(); ContentProviderRecord r = e.getValue(); + if (dumpPackage != null && !dumpPackage.equals(r.appInfo.packageName)) { + continue; + } + if (needSep) { + pw.println(""); + needSep = false; + } + if (header != null) { + pw.println(header); + header = null; + } + written = true; pw.print(" "); pw.print(e.getKey()); pw.print(": "); pw.println(r.toShortString()); } + return written; } - void dumpProvidersLocked(PrintWriter pw, boolean dumpAll) { + boolean dumpProvidersLocked(PrintWriter pw, boolean dumpAll, String dumpPackage) { + boolean needSep = false; + if (mSingletonByClass.size() > 0) { - pw.println(" Published single-user content providers (by class):"); - dumpProvidersByClassLocked(pw, dumpAll, mSingletonByClass); + needSep |= dumpProvidersByClassLocked(pw, dumpAll, dumpPackage, + " Published single-user content providers (by class):", needSep, + mSingletonByClass); } - pw.println(""); for (int i = 0; i < mProvidersByClassPerUser.size(); i++) { HashMap<ComponentName, ContentProviderRecord> map = mProvidersByClassPerUser.valueAt(i); - pw.println(""); - pw.println(" Published user " + mProvidersByClassPerUser.keyAt(i) - + " content providers (by class):"); - dumpProvidersByClassLocked(pw, dumpAll, map); + needSep |= dumpProvidersByClassLocked(pw, dumpAll, dumpPackage, + " Published user " + mProvidersByClassPerUser.keyAt(i) + + " content providers (by class):", needSep, map); } if (dumpAll) { - pw.println(""); - pw.println(" Single-user authority to provider mappings:"); - dumpProvidersByNameLocked(pw, mSingletonByName); + needSep |= dumpProvidersByNameLocked(pw, dumpPackage, + " Single-user authority to provider mappings:", needSep, mSingletonByName); for (int i = 0; i < mProvidersByNamePerUser.size(); i++) { - pw.println(""); - pw.println(" User " + mProvidersByNamePerUser.keyAt(i) - + " authority to provider mappings:"); - dumpProvidersByNameLocked(pw, mProvidersByNamePerUser.valueAt(i)); + needSep |= dumpProvidersByNameLocked(pw, dumpPackage, + " User " + mProvidersByNamePerUser.keyAt(i) + + " authority to provider mappings:", needSep, + mProvidersByNamePerUser.valueAt(i)); } } + return needSep; } protected boolean dumpProvider(FileDescriptor fd, PrintWriter pw, String name, String[] args, diff --git a/services/java/com/android/server/am/ReceiverList.java b/services/java/com/android/server/am/ReceiverList.java index 9b6701e4debc..fa8c1dfc4fda 100644 --- a/services/java/com/android/server/am/ReceiverList.java +++ b/services/java/com/android/server/am/ReceiverList.java @@ -32,7 +32,7 @@ import java.util.ArrayList; * A receiver object that has registered for one or more broadcasts. * The ArrayList holds BroadcastFilter objects. */ -class ReceiverList extends ArrayList<BroadcastFilter> +final class ReceiverList extends ArrayList<BroadcastFilter> implements IBinder.DeathRecipient { final ActivityManagerService owner; public final IIntentReceiver receiver; @@ -69,7 +69,7 @@ class ReceiverList extends ArrayList<BroadcastFilter> } void dumpLocal(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("app="); pw.print(app.toShortString()); + pw.print(prefix); pw.print("app="); pw.print(app != null ? app.toShortString() : null); pw.print(" pid="); pw.print(pid); pw.print(" uid="); pw.print(uid); pw.print(" user="); pw.println(userId); if (curBroadcast != null || linkedToDeath) { diff --git a/services/java/com/android/server/am/ServiceRecord.java b/services/java/com/android/server/am/ServiceRecord.java index 9fdd293f8382..cc1172a8cccd 100644 --- a/services/java/com/android/server/am/ServiceRecord.java +++ b/services/java/com/android/server/am/ServiceRecord.java @@ -16,40 +16,39 @@ package com.android.server.am; -import android.app.PendingIntent; -import android.net.Uri; -import android.provider.Settings; +import com.android.internal.app.ProcessStats; import com.android.internal.os.BatteryStatsImpl; import com.android.server.NotificationManagerService; import android.app.INotificationManager; import android.app.Notification; import android.app.NotificationManager; +import android.app.PendingIntent; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +import android.net.Uri; import android.os.Binder; import android.os.IBinder; import android.os.RemoteException; import android.os.SystemClock; import android.os.UserHandle; +import android.provider.Settings; +import android.util.ArrayMap; import android.util.Slog; import android.util.TimeUtils; import java.io.PrintWriter; import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; import java.util.List; /** * A running application service. */ -class ServiceRecord extends Binder { +final class ServiceRecord extends Binder { // Maximum number of delivery attempts before giving up. static final int MAX_DELIVERY_COUNT = 3; @@ -76,24 +75,30 @@ class ServiceRecord extends Binder { final boolean exported; // from ServiceInfo.exported final Runnable restarter; // used to schedule retries of starting the service final long createTime; // when this service was created - final HashMap<Intent.FilterComparison, IntentBindRecord> bindings - = new HashMap<Intent.FilterComparison, IntentBindRecord>(); + final ArrayMap<Intent.FilterComparison, IntentBindRecord> bindings + = new ArrayMap<Intent.FilterComparison, IntentBindRecord>(); // All active bindings to the service. - final HashMap<IBinder, ArrayList<ConnectionRecord>> connections - = new HashMap<IBinder, ArrayList<ConnectionRecord>>(); + final ArrayMap<IBinder, ArrayList<ConnectionRecord>> connections + = new ArrayMap<IBinder, ArrayList<ConnectionRecord>>(); // IBinder -> ConnectionRecord of all bound clients ProcessRecord app; // where this service is running or null. ProcessRecord isolatedProc; // keep track of isolated process, if requested + ProcessStats.ServiceState tracker; // tracking service execution, may be null + boolean delayed; // are we waiting to start this service in the background? 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. + long startingBgTimeout; // time at which we scheduled this for a delayed start. boolean startRequested; // someone explicitly called start? + boolean delayedStop; // service has been stopped but is in a delayed start? boolean stopIfKilled; // last onStart() said to stop if service killed? boolean callStart; // last onStart() has asked to alway be called on restart. int executeNesting; // number of outstanding operations keeping foreground. + boolean executeFg; // should we be executing in the foreground? long executingStart; // start time of last execute request. + boolean createdFromFg; // was this service last created due to a foreground process call? int crashCount; // number of times proc has crashed with service running int totalRestartCount; // number of times we have had to restart. int restartCount; // number of restarts performed in a row. @@ -218,6 +223,9 @@ class ServiceRecord extends Binder { if (isolatedProc != null) { pw.print(prefix); pw.print("isolatedProc="); pw.println(isolatedProc); } + if (delayed) { + pw.print(prefix); pw.print("delayed="); pw.println(delayed); + } if (isForeground || foregroundId != 0) { pw.print(prefix); pw.print("isForeground="); pw.print(isForeground); pw.print(" foregroundId="); pw.print(foregroundId); @@ -225,24 +233,31 @@ class ServiceRecord extends Binder { } pw.print(prefix); pw.print("createTime="); TimeUtils.formatDuration(createTime, nowReal, pw); - pw.print(" lastActivity="); + pw.print(" startingBgTimeout="); + TimeUtils.formatDuration(startingBgTimeout, now, pw); + pw.println(); + pw.print(prefix); pw.print("lastActivity="); TimeUtils.formatDuration(lastActivity, now, pw); - pw.println(""); - pw.print(prefix); pw.print("executingStart="); - TimeUtils.formatDuration(executingStart, now, pw); pw.print(" restartTime="); TimeUtils.formatDuration(restartTime, now, pw); - pw.println(""); - if (startRequested || lastStartId != 0) { + pw.print(" createdFromFg="); pw.println(createdFromFg); + if (startRequested || delayedStop || lastStartId != 0) { pw.print(prefix); pw.print("startRequested="); pw.print(startRequested); + pw.print(" delayedStop="); pw.print(delayedStop); 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) { + if (executeNesting != 0) { pw.print(prefix); pw.print("executeNesting="); pw.print(executeNesting); - pw.print(" restartCount="); pw.print(restartCount); + pw.print(" executeFg="); pw.print(executeFg); + pw.print(" executingStart="); + TimeUtils.formatDuration(executingStart, now, pw); + pw.println(); + } + if (crashCount != 0 || restartCount != 0 + || restartDelay != 0 || nextRestartTime != 0) { + pw.print(prefix); pw.print("restartCount="); pw.print(restartCount); pw.print(" restartDelay="); TimeUtils.formatDuration(restartDelay, now, pw); pw.print(" nextRestartTime="); @@ -258,10 +273,9 @@ class ServiceRecord extends Binder { 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(); + for (int i=0; i<bindings.size(); i++) { + IntentBindRecord b = bindings.valueAt(i); pw.print(prefix); pw.print("* IntentBindRecord{"); pw.print(Integer.toHexString(System.identityHashCode(b))); if ((b.collectFlags()&Context.BIND_AUTO_CREATE) != 0) { @@ -273,9 +287,8 @@ class ServiceRecord extends Binder { } if (connections.size() > 0) { pw.print(prefix); pw.println("All Connections:"); - Iterator<ArrayList<ConnectionRecord>> it = connections.values().iterator(); - while (it.hasNext()) { - ArrayList<ConnectionRecord> c = it.next(); + for (int conni=0; conni<connections.size(); conni++) { + ArrayList<ConnectionRecord> c = connections.valueAt(conni); for (int i=0; i<c.size(); i++) { pw.print(prefix); pw.print(" "); pw.println(c.get(i)); } @@ -285,7 +298,8 @@ class ServiceRecord extends Binder { ServiceRecord(ActivityManagerService ams, BatteryStatsImpl.Uid.Pkg.Serv servStats, ComponentName name, - Intent.FilterComparison intent, ServiceInfo sInfo, Runnable restarter) { + Intent.FilterComparison intent, ServiceInfo sInfo, boolean callerIsFg, + Runnable restarter) { this.ams = ams; this.stats = servStats; this.name = name; @@ -304,6 +318,26 @@ class ServiceRecord extends Binder { createTime = SystemClock.elapsedRealtime(); lastActivity = SystemClock.uptimeMillis(); userId = UserHandle.getUserId(appInfo.uid); + createdFromFg = callerIsFg; + } + + public ProcessStats.ServiceState getTracker() { + if (tracker != null) { + return tracker; + } + if ((serviceInfo.applicationInfo.flags&ApplicationInfo.FLAG_PERSISTENT) == 0) { + tracker = ams.mProcessStats.getServiceStateLocked(serviceInfo.packageName, + serviceInfo.applicationInfo.uid, serviceInfo.processName, serviceInfo.name); + tracker.applyNewOwner(this); + } + return tracker; + } + + public void forceClearTracker() { + if (tracker != null) { + tracker.clearCurrentOwner(this, true); + tracker = null; + } } public AppBindRecord retrieveAppBindingLocked(Intent intent, @@ -323,6 +357,20 @@ class ServiceRecord extends Binder { return a; } + public boolean hasAutoCreateConnections() { + // XXX should probably keep a count of the number of auto-create + // connections directly in the service. + for (int conni=connections.size()-1; conni>=0; conni--) { + ArrayList<ConnectionRecord> cr = connections.valueAt(conni); + for (int i=0; i<cr.size(); i++) { + if ((cr.get(i).flags&Context.BIND_AUTO_CREATE) != 0) { + return true; + } + } + } + return false; + } + public void resetRestartCounter() { restartCount = 0; restartDelay = 0; diff --git a/services/java/com/android/server/am/StrictModeViolationDialog.java b/services/java/com/android/server/am/StrictModeViolationDialog.java index 35d50a16be09..fda1ec130980 100644 --- a/services/java/com/android/server/am/StrictModeViolationDialog.java +++ b/services/java/com/android/server/am/StrictModeViolationDialog.java @@ -16,16 +16,15 @@ package com.android.server.am; -import static android.view.WindowManager.LayoutParams.FLAG_SYSTEM_ERROR; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_SYSTEM_ERROR; import android.content.Context; import android.content.DialogInterface; import android.content.res.Resources; import android.os.Handler; import android.os.Message; -import android.util.Slog; -class StrictModeViolationDialog extends BaseErrorDialog { +final class StrictModeViolationDialog extends BaseErrorDialog { private final static String TAG = "StrictModeViolationDialog"; private final ActivityManagerService mService; @@ -75,7 +74,7 @@ class StrictModeViolationDialog extends BaseErrorDialog { } setTitle(res.getText(com.android.internal.R.string.aerr_title)); - getWindow().addFlags(FLAG_SYSTEM_ERROR); + getWindow().addPrivateFlags(PRIVATE_FLAG_SYSTEM_ERROR); getWindow().setTitle("Strict Mode Violation: " + app.info.processName); // After the timeout, pretend the user clicked the quit button diff --git a/services/java/com/android/server/am/TaskRecord.java b/services/java/com/android/server/am/TaskRecord.java index 1bae9ca53adf..3d568ffb8e07 100644 --- a/services/java/com/android/server/am/TaskRecord.java +++ b/services/java/com/android/server/am/TaskRecord.java @@ -16,15 +16,24 @@ package com.android.server.am; +import static com.android.server.am.ActivityManagerService.TAG; +import static com.android.server.am.ActivityStackSupervisor.DEBUG_ADD_REMOVE; + +import android.app.Activity; +import android.app.ActivityManager; +import android.app.ActivityOptions; +import android.app.IThumbnailRetriever; import android.content.ComponentName; import android.content.Intent; import android.content.pm.ActivityInfo; +import android.graphics.Bitmap; import android.os.UserHandle; import android.util.Slog; import java.io.PrintWriter; +import java.util.ArrayList; -class TaskRecord extends ThumbnailHolder { +final class TaskRecord extends ThumbnailHolder { final int taskId; // Unique identifier for this task. final String affinity; // The affinity name for this task, or null. Intent intent; // The original intent that started the task. @@ -39,7 +48,21 @@ class TaskRecord extends ThumbnailHolder { String stringName; // caching of toString() result. int userId; // user for which this task was created - + + int numFullscreen; // Number of fullscreen activities. + + /** List of all activities in the task arranged in history order */ + final ArrayList<ActivityRecord> mActivities = new ArrayList<ActivityRecord>(); + + /** Current stack */ + ActivityStack stack; + + /** Takes on same set of values as ActivityRecord.mActivityType */ + private int mTaskType; + + /** Launch the home activity when leaving this task. */ + boolean mOnTopOfHome = false; + TaskRecord(int _taskId, ActivityInfo info, Intent _intent) { taskId = _taskId; affinity = info.taskAffinity; @@ -49,11 +72,11 @@ class TaskRecord extends ThumbnailHolder { void touchActiveTime() { lastActiveTime = android.os.SystemClock.elapsedRealtime(); } - + long getInactiveDuration() { return android.os.SystemClock.elapsedRealtime() - lastActiveTime; } - + void setIntent(Intent _intent, ActivityInfo info) { stringName = null; @@ -104,12 +127,320 @@ class TaskRecord extends ThumbnailHolder { userId = UserHandle.getUserId(info.applicationInfo.uid); } } - + + void disposeThumbnail() { + super.disposeThumbnail(); + for (int i=mActivities.size()-1; i>=0; i--) { + ThumbnailHolder thumb = mActivities.get(i).thumbHolder; + if (thumb != this) { + thumb.disposeThumbnail(); + } + } + } + + ActivityRecord getTopActivity() { + for (int i = mActivities.size() - 1; i >= 0; --i) { + final ActivityRecord r = mActivities.get(i); + if (r.finishing) { + continue; + } + return r; + } + return null; + } + + ActivityRecord topRunningActivityLocked(ActivityRecord notTop) { + for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) { + ActivityRecord r = mActivities.get(activityNdx); + if (!r.finishing && r != notTop && stack.okToShow(r)) { + return r; + } + } + return null; + } + + /** + * Reorder the history stack so that the activity at the given index is + * brought to the front. + */ + final void moveActivityToFrontLocked(ActivityRecord newTop) { + if (DEBUG_ADD_REMOVE) Slog.i(TAG, "Removing and adding activity " + newTop + + " to stack at top", new RuntimeException("here").fillInStackTrace()); + + getTopActivity().frontOfTask = false; + mActivities.remove(newTop); + mActivities.add(newTop); + newTop.frontOfTask = true; + } + + void addActivityAtBottom(ActivityRecord r) { + addActivityAtIndex(0, r); + } + + void addActivityToTop(ActivityRecord r) { + addActivityAtIndex(mActivities.size(), r); + } + + void addActivityAtIndex(int index, ActivityRecord r) { + // Remove r first, and if it wasn't already in the list and it's fullscreen, count it. + if (!mActivities.remove(r) && r.fullscreen) { + // Was not previously in list. + numFullscreen++; + } + // Only set this based on the first activity + if (mActivities.isEmpty()) { + mTaskType = r.mActivityType; + } else { + // Otherwise make all added activities match this one. + r.mActivityType = mTaskType; + } + mActivities.add(index, r); + } + + /** @return true if this was the last activity in the task */ + boolean removeActivity(ActivityRecord r) { + if (mActivities.remove(r) && r.fullscreen) { + // Was previously in list. + numFullscreen--; + } + return mActivities.size() == 0; + } + + /** + * Completely remove all activities associated with an existing + * task starting at a specified index. + */ + final void performClearTaskAtIndexLocked(int activityNdx) { + int numActivities = mActivities.size(); + for ( ; activityNdx < numActivities; ++activityNdx) { + final ActivityRecord r = mActivities.get(activityNdx); + if (r.finishing) { + continue; + } + if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear", false)) { + --activityNdx; + --numActivities; + } + } + } + + /** + * Completely remove all activities associated with an existing task. + */ + final void performClearTaskLocked() { + performClearTaskAtIndexLocked(0); + } + + /** + * Perform clear operation as requested by + * {@link Intent#FLAG_ACTIVITY_CLEAR_TOP}: search from the top of the + * stack to the given task, then look for + * an instance of that activity in the stack and, if found, finish all + * activities on top of it and return the instance. + * + * @param newR Description of the new activity being started. + * @return Returns the old activity that should be continued to be used, + * or null if none was found. + */ + final ActivityRecord performClearTaskLocked(ActivityRecord newR, int launchFlags) { + int numActivities = mActivities.size(); + for (int activityNdx = numActivities - 1; activityNdx >= 0; --activityNdx) { + ActivityRecord r = mActivities.get(activityNdx); + if (r.finishing) { + continue; + } + if (r.realActivity.equals(newR.realActivity)) { + // Here it is! Now finish everything in front... + final ActivityRecord ret = r; + + for (++activityNdx; activityNdx < numActivities; ++activityNdx) { + r = mActivities.get(activityNdx); + if (r.finishing) { + continue; + } + ActivityOptions opts = r.takeOptionsLocked(); + if (opts != null) { + ret.updateOptionsLocked(opts); + } + if (stack.finishActivityLocked(r, Activity.RESULT_CANCELED, null, "clear", + false)) { + --activityNdx; + --numActivities; + } + } + + // 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 + && (launchFlags & Intent.FLAG_ACTIVITY_SINGLE_TOP) == 0) { + if (!ret.finishing) { + stack.finishActivityLocked(ret, Activity.RESULT_CANCELED, null, + "clear", false); + return null; + } + } + + return ret; + } + } + + return null; + } + + public ActivityManager.TaskThumbnails getTaskThumbnailsLocked() { + TaskAccessInfo info = getTaskAccessInfoLocked(true); + final ActivityRecord resumedActivity = stack.mResumedActivity; + if (resumedActivity != null && resumedActivity.thumbHolder == this) { + info.mainThumbnail = stack.screenshotActivities(resumedActivity); + } + if (info.mainThumbnail == null) { + info.mainThumbnail = lastThumbnail; + } + return info; + } + + public Bitmap getTaskTopThumbnailLocked() { + final ActivityRecord resumedActivity = stack.mResumedActivity; + if (resumedActivity != null && resumedActivity.task == this) { + // This task is the current resumed task, we just need to take + // a screenshot of it and return that. + return stack.screenshotActivities(resumedActivity); + } + // Return the information about the task, to figure out the top + // thumbnail to return. + TaskAccessInfo info = getTaskAccessInfoLocked(true); + if (info.numSubThumbbails <= 0) { + return info.mainThumbnail != null ? info.mainThumbnail : lastThumbnail; + } + return info.subtasks.get(info.numSubThumbbails-1).holder.lastThumbnail; + } + + public ActivityRecord removeTaskActivitiesLocked(int subTaskIndex, + boolean taskRequired) { + TaskAccessInfo info = getTaskAccessInfoLocked(false); + if (info.root == null) { + if (taskRequired) { + Slog.w(TAG, "removeTaskLocked: unknown taskId " + taskId); + } + return null; + } + + if (subTaskIndex < 0) { + // Just remove the entire task. + performClearTaskAtIndexLocked(info.rootIndex); + return info.root; + } + + if (subTaskIndex >= info.subtasks.size()) { + if (taskRequired) { + Slog.w(TAG, "removeTaskLocked: unknown subTaskIndex " + subTaskIndex); + } + return null; + } + + // Remove all of this task's activities starting at the sub task. + TaskAccessInfo.SubTask subtask = info.subtasks.get(subTaskIndex); + performClearTaskAtIndexLocked(subtask.index); + return subtask.activity; + } + + boolean isHomeTask() { + return mTaskType == ActivityRecord.HOME_ACTIVITY_TYPE; + } + + boolean isApplicationTask() { + return mTaskType == ActivityRecord.APPLICATION_ACTIVITY_TYPE; + } + + public TaskAccessInfo getTaskAccessInfoLocked(boolean inclThumbs) { + final TaskAccessInfo thumbs = new TaskAccessInfo(); + // How many different sub-thumbnails? + final int NA = mActivities.size(); + int j = 0; + ThumbnailHolder holder = null; + while (j < NA) { + ActivityRecord ar = mActivities.get(j); + if (!ar.finishing) { + thumbs.root = ar; + thumbs.rootIndex = j; + holder = ar.thumbHolder; + if (holder != null) { + thumbs.mainThumbnail = holder.lastThumbnail; + } + j++; + break; + } + j++; + } + + if (j >= NA) { + return thumbs; + } + + ArrayList<TaskAccessInfo.SubTask> subtasks = new ArrayList<TaskAccessInfo.SubTask>(); + thumbs.subtasks = subtasks; + while (j < NA) { + ActivityRecord ar = mActivities.get(j); + j++; + if (ar.finishing) { + continue; + } + if (ar.thumbHolder != holder && holder != null) { + thumbs.numSubThumbbails++; + holder = ar.thumbHolder; + TaskAccessInfo.SubTask sub = new TaskAccessInfo.SubTask(); + sub.holder = holder; + sub.activity = ar; + sub.index = j-1; + subtasks.add(sub); + } + } + if (thumbs.numSubThumbbails > 0) { + thumbs.retriever = new IThumbnailRetriever.Stub() { + @Override + public Bitmap getThumbnail(int index) { + if (index < 0 || index >= thumbs.subtasks.size()) { + return null; + } + TaskAccessInfo.SubTask sub = thumbs.subtasks.get(index); + ActivityRecord resumedActivity = stack.mResumedActivity; + if (resumedActivity != null && resumedActivity.thumbHolder == sub.holder) { + return stack.screenshotActivities(resumedActivity); + } + return sub.holder.lastThumbnail; + } + }; + } + return thumbs; + } + + /** + * Find the activity in the history stack within the given task. Returns + * the index within the history at which it's found, or < 0 if not found. + */ + final ActivityRecord findActivityInHistoryLocked(ActivityRecord r) { + final ComponentName realActivity = r.realActivity; + for (int activityNdx = mActivities.size() - 1; activityNdx >= 0; --activityNdx) { + ActivityRecord candidate = mActivities.get(activityNdx); + if (candidate.finishing) { + continue; + } + if (candidate.realActivity.equals(realActivity)) { + return candidate; + } + } + return null; + } + void dump(PrintWriter pw, String prefix) { - if (numActivities != 0 || rootWasReset || userId != 0) { + if (numActivities != 0 || rootWasReset || userId != 0 || numFullscreen != 0) { pw.print(prefix); pw.print("numActivities="); pw.print(numActivities); pw.print(" rootWasReset="); pw.print(rootWasReset); - pw.print(" userId="); pw.println(userId); + pw.print(" userId="); pw.print(userId); + pw.print(" mTaskType="); pw.print(mTaskType); + pw.print(" numFullscreen="); pw.print(numFullscreen); + pw.print(" mOnTopOfHome="); pw.println(mOnTopOfHome); } if (affinity != null) { pw.print(prefix); pw.print("affinity="); pw.println(affinity); @@ -136,6 +467,7 @@ class TaskRecord extends ThumbnailHolder { pw.print(prefix); pw.print("realActivity="); pw.println(realActivity.flattenToShortString()); } + pw.print(prefix); pw.print("Activities="); pw.println(mActivities); if (!askedCompatMode) { pw.print(prefix); pw.print("askedCompatMode="); pw.println(askedCompatMode); } @@ -146,30 +478,35 @@ class TaskRecord extends ThumbnailHolder { pw.print((getInactiveDuration()/1000)); pw.println("s)"); } + @Override public String toString() { + StringBuilder sb = new StringBuilder(128); if (stringName != null) { - return stringName; + sb.append(stringName); + sb.append(" U="); + sb.append(userId); + sb.append(" sz="); + sb.append(mActivities.size()); + sb.append('}'); + return sb.toString(); } - StringBuilder sb = new StringBuilder(128); sb.append("TaskRecord{"); sb.append(Integer.toHexString(System.identityHashCode(this))); sb.append(" #"); sb.append(taskId); if (affinity != null) { - sb.append(" A "); + sb.append(" A="); sb.append(affinity); } else if (intent != null) { - sb.append(" I "); + sb.append(" I="); sb.append(intent.getComponent().flattenToShortString()); } else if (affinityIntent != null) { - sb.append(" aI "); + sb.append(" aI="); sb.append(affinityIntent.getComponent().flattenToShortString()); } else { sb.append(" ??"); } - sb.append(" U "); - sb.append(userId); - sb.append('}'); - return stringName = sb.toString(); + stringName = sb.toString(); + return toString(); } } diff --git a/services/java/com/android/server/am/ThumbnailHolder.java b/services/java/com/android/server/am/ThumbnailHolder.java index 02f4fcbf1a7c..a6974f56356d 100644 --- a/services/java/com/android/server/am/ThumbnailHolder.java +++ b/services/java/com/android/server/am/ThumbnailHolder.java @@ -21,4 +21,9 @@ import android.graphics.Bitmap; public class ThumbnailHolder { Bitmap lastThumbnail; // Last thumbnail captured for this item. CharSequence lastDescription; // Last description captured for this item. + + void disposeThumbnail() { + lastThumbnail = null; + lastDescription = null; + } } diff --git a/services/java/com/android/server/am/TransferPipe.java b/services/java/com/android/server/am/TransferPipe.java deleted file mode 100644 index c3f4f935d97d..000000000000 --- a/services/java/com/android/server/am/TransferPipe.java +++ /dev/null @@ -1,242 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.am; - -import java.io.FileDescriptor; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.IOException; - -import android.os.Binder; -import android.os.IBinder; -import android.os.IInterface; -import android.os.ParcelFileDescriptor; -import android.os.RemoteException; -import android.os.SystemClock; -import android.util.Slog; - -/** - * Helper for transferring data through a pipe from a client app. - */ -class TransferPipe implements Runnable { - static final String TAG = "TransferPipe"; - static final boolean DEBUG = false; - - static final long DEFAULT_TIMEOUT = 5000; // 5 seconds - - final Thread mThread;; - final ParcelFileDescriptor[] mFds; - - FileDescriptor mOutFd; - long mEndTime; - String mFailure; - boolean mComplete; - - String mBufferPrefix; - - interface Caller { - void go(IInterface iface, FileDescriptor fd, String prefix, - String[] args) throws RemoteException; - } - - TransferPipe() throws IOException { - mThread = new Thread(this, "TransferPipe"); - mFds = ParcelFileDescriptor.createPipe(); - } - - ParcelFileDescriptor getReadFd() { - return mFds[0]; - } - - ParcelFileDescriptor getWriteFd() { - return mFds[1]; - } - - void setBufferPrefix(String prefix) { - mBufferPrefix = prefix; - } - - static void go(Caller caller, IInterface iface, FileDescriptor out, - String prefix, String[] args) throws IOException, RemoteException { - go(caller, iface, out, prefix, args, DEFAULT_TIMEOUT); - } - - static void go(Caller caller, IInterface iface, FileDescriptor out, - String prefix, String[] args, long timeout) throws IOException, RemoteException { - if ((iface.asBinder()) instanceof Binder) { - // This is a local object... just call it directly. - try { - caller.go(iface, out, prefix, args); - } catch (RemoteException e) { - } - return; - } - - TransferPipe tp = new TransferPipe(); - try { - caller.go(iface, tp.getWriteFd().getFileDescriptor(), prefix, args); - tp.go(out, timeout); - } finally { - tp.kill(); - } - } - - static void goDump(IBinder binder, FileDescriptor out, - String[] args) throws IOException, RemoteException { - goDump(binder, out, args, DEFAULT_TIMEOUT); - } - - static void goDump(IBinder binder, FileDescriptor out, - String[] args, long timeout) throws IOException, RemoteException { - if (binder instanceof Binder) { - // This is a local object... just call it directly. - try { - binder.dump(out, args); - } catch (RemoteException e) { - } - return; - } - - TransferPipe tp = new TransferPipe(); - try { - binder.dumpAsync(tp.getWriteFd().getFileDescriptor(), args); - tp.go(out, timeout); - } finally { - tp.kill(); - } - } - - void go(FileDescriptor out) throws IOException { - go(out, DEFAULT_TIMEOUT); - } - - void go(FileDescriptor out, long timeout) throws IOException { - try { - synchronized (this) { - mOutFd = out; - mEndTime = SystemClock.uptimeMillis() + timeout; - - if (DEBUG) Slog.i(TAG, "read=" + getReadFd() + " write=" + getWriteFd() - + " out=" + out); - - // Close the write fd, so we know when the other side is done. - closeFd(1); - - mThread.start(); - - while (mFailure == null && !mComplete) { - long waitTime = mEndTime - SystemClock.uptimeMillis(); - if (waitTime <= 0) { - if (DEBUG) Slog.i(TAG, "TIMEOUT!"); - mThread.interrupt(); - throw new IOException("Timeout"); - } - - try { - wait(waitTime); - } catch (InterruptedException e) { - } - } - - if (DEBUG) Slog.i(TAG, "Finished: " + mFailure); - if (mFailure != null) { - throw new IOException(mFailure); - } - } - } finally { - kill(); - } - } - - void closeFd(int num) { - if (mFds[num] != null) { - if (DEBUG) Slog.i(TAG, "Closing: " + mFds[num]); - try { - mFds[num].close(); - } catch (IOException e) { - } - mFds[num] = null; - } - } - - void kill() { - closeFd(0); - closeFd(1); - } - - @Override - public void run() { - final byte[] buffer = new byte[1024]; - final FileInputStream fis = new FileInputStream(getReadFd().getFileDescriptor()); - final FileOutputStream fos = new FileOutputStream(mOutFd); - - if (DEBUG) Slog.i(TAG, "Ready to read pipe..."); - byte[] bufferPrefix = null; - boolean needPrefix = true; - if (mBufferPrefix != null) { - bufferPrefix = mBufferPrefix.getBytes(); - } - - int size; - try { - while ((size=fis.read(buffer)) > 0) { - if (DEBUG) Slog.i(TAG, "Got " + size + " bytes"); - if (bufferPrefix == null) { - fos.write(buffer, 0, size); - } else { - int start = 0; - for (int i=0; i<size; i++) { - if (buffer[i] != '\n') { - if (i > start) { - fos.write(buffer, start, i-start); - } - start = i; - if (needPrefix) { - fos.write(bufferPrefix); - needPrefix = false; - } - do { - i++; - } while (i<size && buffer[i] != '\n'); - if (i < size) { - needPrefix = true; - } - } - } - if (size > start) { - fos.write(buffer, start, size-start); - } - } - } - if (DEBUG) Slog.i(TAG, "End of pipe: size=" + size); - if (mThread.isInterrupted()) { - if (DEBUG) Slog.i(TAG, "Interrupted!"); - } - } catch (IOException e) { - synchronized (this) { - mFailure = e.toString(); - notifyAll(); - return; - } - } - - synchronized (this) { - mComplete = true; - notifyAll(); - } - } -} diff --git a/services/java/com/android/server/am/UriPermission.java b/services/java/com/android/server/am/UriPermission.java index c5b1c7ba0e99..1f12b74432a3 100644 --- a/services/java/com/android/server/am/UriPermission.java +++ b/services/java/com/android/server/am/UriPermission.java @@ -18,8 +18,13 @@ package com.android.server.am; import android.content.Intent; import android.net.Uri; +import android.os.UserHandle; +import android.util.Log; + +import com.google.android.collect.Sets; import java.io.PrintWriter; +import java.util.Comparator; import java.util.HashSet; /** @@ -30,44 +35,241 @@ import java.util.HashSet; * Test cases are at cts/tests/appsecurity-tests/test-apps/UsePermissionDiffCert/ * src/com/android/cts/usespermissiondiffcertapp/AccessPermissionWithDiffSigTest.java */ -class UriPermission { - final int uid; +final class UriPermission { + private static final String TAG = "UriPermission"; + + public static final int STRENGTH_NONE = 0; + public static final int STRENGTH_OWNED = 1; + public static final int STRENGTH_GLOBAL = 2; + public static final int STRENGTH_PERSISTABLE = 3; + + final int userHandle; + final String sourcePkg; + final String targetPkg; + + /** Cached UID of {@link #targetPkg}; should not be persisted */ + final int targetUid; + final Uri uri; + + /** + * Allowed modes. All permission enforcement should use this field. Must + * always be a combination of {@link #ownedModeFlags}, + * {@link #globalModeFlags}, {@link #persistableModeFlags}, and + * {@link #persistedModeFlags}. Mutations <em>must</em> only be performed by + * the owning class. + */ int modeFlags = 0; + + /** Allowed modes with explicit owner. */ + int ownedModeFlags = 0; + /** Allowed modes without explicit owner. */ int globalModeFlags = 0; - final HashSet<UriPermissionOwner> readOwners = new HashSet<UriPermissionOwner>(); - final HashSet<UriPermissionOwner> writeOwners = new HashSet<UriPermissionOwner>(); - - String stringName; - - UriPermission(int _uid, Uri _uri) { - uid = _uid; - uri = _uri; - } - - void clearModes(int modeFlagsToClear) { - if ((modeFlagsToClear&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) { + /** Allowed modes that have been offered for possible persisting. */ + int persistableModeFlags = 0; + /** Allowed modes that should be persisted across device boots. */ + int persistedModeFlags = 0; + + /** + * Timestamp when {@link #persistedModeFlags} was first defined in + * {@link System#currentTimeMillis()} time base. + */ + long persistedCreateTime = INVALID_TIME; + + private static final long INVALID_TIME = Long.MIN_VALUE; + + private HashSet<UriPermissionOwner> mReadOwners; + private HashSet<UriPermissionOwner> mWriteOwners; + + private String stringName; + + UriPermission(String sourcePkg, String targetPkg, int targetUid, Uri uri) { + this.userHandle = UserHandle.getUserId(targetUid); + this.sourcePkg = sourcePkg; + this.targetPkg = targetPkg; + this.targetUid = targetUid; + this.uri = uri; + } + + private void updateModeFlags() { + modeFlags = ownedModeFlags | globalModeFlags | persistableModeFlags | persistedModeFlags; + } + + /** + * Initialize persisted modes as read from file. This doesn't issue any + * global or owner grants. + */ + void initPersistedModes(int modeFlags, long createdTime) { + persistableModeFlags = modeFlags; + persistedModeFlags = modeFlags; + persistedCreateTime = createdTime; + + updateModeFlags(); + } + + void grantModes(int modeFlags, boolean persistable, UriPermissionOwner owner) { + if (persistable) { + persistableModeFlags |= modeFlags; + } + + if (owner == null) { + globalModeFlags |= modeFlags; + } else { + if ((modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) { + addReadOwner(owner); + } + if ((modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) { + addWriteOwner(owner); + } + } + + updateModeFlags(); + } + + /** + * @return if mode changes should trigger persisting. + */ + boolean takePersistableModes(int modeFlags) { + if ((modeFlags & persistableModeFlags) != modeFlags) { + throw new SecurityException("Requested flags 0x" + + Integer.toHexString(modeFlags) + ", but only 0x" + + Integer.toHexString(persistableModeFlags) + " are allowed"); + } + + final int before = persistedModeFlags; + persistedModeFlags |= (persistableModeFlags & modeFlags); + + if (persistedModeFlags != 0) { + persistedCreateTime = System.currentTimeMillis(); + } + + updateModeFlags(); + return persistedModeFlags != before; + } + + boolean releasePersistableModes(int modeFlags) { + final int before = persistedModeFlags; + + persistableModeFlags &= ~modeFlags; + persistedModeFlags &= ~modeFlags; + + if (persistedModeFlags == 0) { + persistedCreateTime = INVALID_TIME; + } + + updateModeFlags(); + return persistedModeFlags != before; + } + + /** + * @return if mode changes should trigger persisting. + */ + boolean clearModes(int modeFlags, boolean persistable) { + final int before = persistedModeFlags; + + if ((modeFlags & Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0) { + if (persistable) { + persistableModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; + persistedModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; + } globalModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; - modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; - if (readOwners.size() > 0) { - for (UriPermissionOwner r : readOwners) { + if (mReadOwners != null) { + ownedModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; + for (UriPermissionOwner r : mReadOwners) { r.removeReadPermission(this); } - readOwners.clear(); + mReadOwners = null; } } - if ((modeFlagsToClear&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) { + if ((modeFlags & Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0) { + if (persistable) { + persistableModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; + persistedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; + } globalModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; - modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; - if (writeOwners.size() > 0) { - for (UriPermissionOwner r : writeOwners) { + if (mWriteOwners != null) { + ownedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; + for (UriPermissionOwner r : mWriteOwners) { r.removeWritePermission(this); } - writeOwners.clear(); + mWriteOwners = null; } } + + if (persistedModeFlags == 0) { + persistedCreateTime = INVALID_TIME; + } + + updateModeFlags(); + return persistedModeFlags != before; + } + + /** + * Return strength of this permission grant for the given flags. + */ + public int getStrength(int modeFlags) { + if ((persistableModeFlags & modeFlags) == modeFlags) { + return STRENGTH_PERSISTABLE; + } else if ((globalModeFlags & modeFlags) == modeFlags) { + return STRENGTH_GLOBAL; + } else if ((ownedModeFlags & modeFlags) == modeFlags) { + return STRENGTH_OWNED; + } else { + return STRENGTH_NONE; + } + } + + private void addReadOwner(UriPermissionOwner owner) { + if (mReadOwners == null) { + mReadOwners = Sets.newHashSet(); + ownedModeFlags |= Intent.FLAG_GRANT_READ_URI_PERMISSION; + updateModeFlags(); + } + if (mReadOwners.add(owner)) { + owner.addReadPermission(this); + } + } + + /** + * Remove given read owner, updating {@Link #modeFlags} as needed. + */ + void removeReadOwner(UriPermissionOwner owner) { + if (!mReadOwners.remove(owner)) { + Log.wtf(TAG, "Unknown read owner " + owner + " in " + this); + } + if (mReadOwners.size() == 0) { + mReadOwners = null; + ownedModeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; + updateModeFlags(); + } + } + + private void addWriteOwner(UriPermissionOwner owner) { + if (mWriteOwners == null) { + mWriteOwners = Sets.newHashSet(); + ownedModeFlags |= Intent.FLAG_GRANT_WRITE_URI_PERMISSION; + updateModeFlags(); + } + if (mWriteOwners.add(owner)) { + owner.addWritePermission(this); + } + } + + /** + * Remove given write owner, updating {@Link #modeFlags} as needed. + */ + void removeWriteOwner(UriPermissionOwner owner) { + if (!mWriteOwners.remove(owner)) { + Log.wtf(TAG, "Unknown write owner " + owner + " in " + this); + } + if (mWriteOwners.size() == 0) { + mWriteOwners = null; + ownedModeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; + updateModeFlags(); + } } - + + @Override public String toString() { if (stringName != null) { return stringName; @@ -82,22 +284,74 @@ class UriPermission { } void dump(PrintWriter pw, String prefix) { - pw.print(prefix); pw.print("modeFlags=0x"); - pw.print(Integer.toHexString(modeFlags)); - pw.print(" uid="); pw.print(uid); - pw.print(" globalModeFlags=0x"); - pw.println(Integer.toHexString(globalModeFlags)); - if (readOwners.size() != 0) { - pw.print(prefix); pw.println("readOwners:"); - for (UriPermissionOwner owner : readOwners) { - pw.print(prefix); pw.print(" * "); pw.println(owner); + pw.print(prefix); + pw.print("userHandle=" + userHandle); + pw.print(" sourcePkg=" + sourcePkg); + pw.println(" targetPkg=" + targetPkg); + + pw.print(prefix); + pw.print("mode=0x" + Integer.toHexString(modeFlags)); + pw.print(" owned=0x" + Integer.toHexString(ownedModeFlags)); + pw.print(" global=0x" + Integer.toHexString(globalModeFlags)); + pw.print(" persistable=0x" + Integer.toHexString(persistableModeFlags)); + pw.print(" persisted=0x" + Integer.toHexString(persistedModeFlags)); + if (persistedCreateTime != INVALID_TIME) { + pw.print(" persistedCreate=" + persistedCreateTime); + } + pw.println(); + + if (mReadOwners != null) { + pw.print(prefix); + pw.println("readOwners:"); + for (UriPermissionOwner owner : mReadOwners) { + pw.print(prefix); + pw.println(" * " + owner); } } - if (writeOwners.size() != 0) { - pw.print(prefix); pw.println("writeOwners:"); - for (UriPermissionOwner owner : writeOwners) { - pw.print(prefix); pw.print(" * "); pw.println(owner); + if (mWriteOwners != null) { + pw.print(prefix); + pw.println("writeOwners:"); + for (UriPermissionOwner owner : mReadOwners) { + pw.print(prefix); + pw.println(" * " + owner); } } } + + public static class PersistedTimeComparator implements Comparator<UriPermission> { + @Override + public int compare(UriPermission lhs, UriPermission rhs) { + return Long.compare(lhs.persistedCreateTime, rhs.persistedCreateTime); + } + } + + /** + * Snapshot of {@link UriPermission} with frozen + * {@link UriPermission#persistedModeFlags} state. + */ + public static class Snapshot { + final int userHandle; + final String sourcePkg; + final String targetPkg; + final Uri uri; + final int persistedModeFlags; + final long persistedCreateTime; + + private Snapshot(UriPermission perm) { + this.userHandle = perm.userHandle; + this.sourcePkg = perm.sourcePkg; + this.targetPkg = perm.targetPkg; + this.uri = perm.uri; + this.persistedModeFlags = perm.persistedModeFlags; + this.persistedCreateTime = perm.persistedCreateTime; + } + } + + public Snapshot snapshot() { + return new Snapshot(this); + } + + public android.content.UriPermission buildPersistedPublicApiObject() { + return new android.content.UriPermission(uri, persistedModeFlags, persistedCreateTime); + } } diff --git a/services/java/com/android/server/am/UriPermissionOwner.java b/services/java/com/android/server/am/UriPermissionOwner.java index 68a2e0fdc426..7bbd3bcc2eb9 100644 --- a/services/java/com/android/server/am/UriPermissionOwner.java +++ b/services/java/com/android/server/am/UriPermissionOwner.java @@ -24,7 +24,7 @@ import android.os.IBinder; import java.util.HashSet; import java.util.Iterator; -class UriPermissionOwner { +final class UriPermissionOwner { final ActivityManagerService service; final Object owner; @@ -67,24 +67,16 @@ class UriPermissionOwner { if ((mode&Intent.FLAG_GRANT_READ_URI_PERMISSION) != 0 && readUriPermissions != null) { for (UriPermission perm : readUriPermissions) { - perm.readOwners.remove(this); - if (perm.readOwners.size() == 0 && (perm.globalModeFlags - &Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0) { - perm.modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; - service.removeUriPermissionIfNeededLocked(perm); - } + perm.removeReadOwner(this); + service.removeUriPermissionIfNeededLocked(perm); } readUriPermissions = null; } if ((mode&Intent.FLAG_GRANT_WRITE_URI_PERMISSION) != 0 && writeUriPermissions != null) { for (UriPermission perm : writeUriPermissions) { - perm.writeOwners.remove(this); - if (perm.writeOwners.size() == 0 && (perm.globalModeFlags - &Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0) { - perm.modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; - service.removeUriPermissionIfNeededLocked(perm); - } + perm.removeWriteOwner(this); + service.removeUriPermissionIfNeededLocked(perm); } writeUriPermissions = null; } @@ -97,12 +89,8 @@ class UriPermissionOwner { while (it.hasNext()) { UriPermission perm = it.next(); if (uri.equals(perm.uri)) { - perm.readOwners.remove(this); - if (perm.readOwners.size() == 0 && (perm.globalModeFlags - &Intent.FLAG_GRANT_READ_URI_PERMISSION) == 0) { - perm.modeFlags &= ~Intent.FLAG_GRANT_READ_URI_PERMISSION; - service.removeUriPermissionIfNeededLocked(perm); - } + perm.removeReadOwner(this); + service.removeUriPermissionIfNeededLocked(perm); it.remove(); } } @@ -116,12 +104,8 @@ class UriPermissionOwner { while (it.hasNext()) { UriPermission perm = it.next(); if (uri.equals(perm.uri)) { - perm.writeOwners.remove(this); - if (perm.writeOwners.size() == 0 && (perm.globalModeFlags - &Intent.FLAG_GRANT_WRITE_URI_PERMISSION) == 0) { - perm.modeFlags &= ~Intent.FLAG_GRANT_WRITE_URI_PERMISSION; - service.removeUriPermissionIfNeededLocked(perm); - } + perm.removeWriteOwner(this); + service.removeUriPermissionIfNeededLocked(perm); it.remove(); } } diff --git a/services/java/com/android/server/am/UsageStatsService.java b/services/java/com/android/server/am/UsageStatsService.java index 6dae4aa1754c..e96d8b1ce91c 100644 --- a/services/java/com/android/server/am/UsageStatsService.java +++ b/services/java/com/android/server/am/UsageStatsService.java @@ -16,8 +16,10 @@ package com.android.server.am; +import android.app.AppGlobals; import android.content.ComponentName; import android.content.Context; +import android.content.pm.IPackageManager; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.os.Binder; @@ -25,8 +27,10 @@ import android.os.IBinder; import android.os.FileUtils; import android.os.Parcel; import android.os.Process; +import android.os.RemoteException; import android.os.ServiceManager; import android.os.SystemClock; +import android.util.ArrayMap; import android.util.AtomicFile; import android.util.Slog; import android.util.Xml; @@ -73,7 +77,7 @@ public final class UsageStatsService extends IUsageStats.Stub { private static final String TAG = "UsageStats"; // Current on-disk Parcel version - private static final int VERSION = 1007; + private static final int VERSION = 1008; private static final int CHECKIN_VERSION = 4; @@ -93,10 +97,10 @@ public final class UsageStatsService extends IUsageStats.Stub { static IUsageStats sService; private Context mContext; // structure used to maintain statistics since the last checkin. - final private Map<String, PkgUsageStatsExtended> mStats; + final private ArrayMap<String, PkgUsageStatsExtended> mStats; // Maintains the last time any component was resumed, for all time. - final private Map<String, Map<String, Long>> mLastResumeTimes; + final private ArrayMap<String, ArrayMap<String, Long>> mLastResumeTimes; // To remove last-resume time stats when a pacakge is removed. private PackageMonitor mPackageMonitor; @@ -162,8 +166,10 @@ public final class UsageStatsService extends IUsageStats.Stub { } private class PkgUsageStatsExtended { - final HashMap<String, TimeStats> mLaunchTimes - = new HashMap<String, TimeStats>(); + final ArrayMap<String, TimeStats> mLaunchTimes + = new ArrayMap<String, TimeStats>(); + final ArrayMap<String, TimeStats> mFullyDrawnTimes + = new ArrayMap<String, TimeStats>(); int mLaunchCount; long mUsageTime; long mPausedTime; @@ -180,14 +186,25 @@ public final class UsageStatsService extends IUsageStats.Stub { if (localLOGV) Slog.v(TAG, "Launch count: " + mLaunchCount + ", Usage time:" + mUsageTime); - final int numTimeStats = in.readInt(); - if (localLOGV) Slog.v(TAG, "Reading comps: " + numTimeStats); - for (int i=0; i<numTimeStats; i++) { + final int numLaunchTimeStats = in.readInt(); + if (localLOGV) Slog.v(TAG, "Reading launch times: " + numLaunchTimeStats); + mLaunchTimes.ensureCapacity(numLaunchTimeStats); + for (int i=0; i<numLaunchTimeStats; i++) { String comp = in.readString(); if (localLOGV) Slog.v(TAG, "Component: " + comp); TimeStats times = new TimeStats(in); mLaunchTimes.put(comp, times); } + + final int numFullyDrawnTimeStats = in.readInt(); + if (localLOGV) Slog.v(TAG, "Reading fully drawn times: " + numFullyDrawnTimeStats); + mFullyDrawnTimes.ensureCapacity(numFullyDrawnTimeStats); + for (int i=0; i<numFullyDrawnTimeStats; i++) { + String comp = in.readString(); + if (localLOGV) Slog.v(TAG, "Component: " + comp); + TimeStats times = new TimeStats(in); + mFullyDrawnTimes.put(comp, times); + } } void updateResume(String comp, boolean launched) { @@ -219,31 +236,44 @@ public final class UsageStatsService extends IUsageStats.Stub { } times.add(millis); } - + + void addFullyDrawnTime(String comp, int millis) { + TimeStats times = mFullyDrawnTimes.get(comp); + if (times == null) { + times = new TimeStats(); + mFullyDrawnTimes.put(comp, times); + } + times.add(millis); + } + void writeToParcel(Parcel out) { out.writeInt(mLaunchCount); out.writeLong(mUsageTime); - final int numTimeStats = mLaunchTimes.size(); - out.writeInt(numTimeStats); - if (numTimeStats > 0) { - for (Map.Entry<String, TimeStats> ent : mLaunchTimes.entrySet()) { - out.writeString(ent.getKey()); - TimeStats times = ent.getValue(); - times.writeToParcel(out); - } + final int numLaunchTimeStats = mLaunchTimes.size(); + out.writeInt(numLaunchTimeStats); + for (int i=0; i<numLaunchTimeStats; i++) { + out.writeString(mLaunchTimes.keyAt(i)); + mLaunchTimes.valueAt(i).writeToParcel(out); + } + final int numFullyDrawnTimeStats = mFullyDrawnTimes.size(); + out.writeInt(numFullyDrawnTimeStats); + for (int i=0; i<numFullyDrawnTimeStats; i++) { + out.writeString(mFullyDrawnTimes.keyAt(i)); + mFullyDrawnTimes.valueAt(i).writeToParcel(out); } } void clear() { mLaunchTimes.clear(); + mFullyDrawnTimes.clear(); mLaunchCount = 0; mUsageTime = 0; } } UsageStatsService(String dir) { - mStats = new HashMap<String, PkgUsageStatsExtended>(); - mLastResumeTimes = new HashMap<String, Map<String, Long>>(); + mStats = new ArrayMap<String, PkgUsageStatsExtended>(); + mLastResumeTimes = new ArrayMap<String, ArrayMap<String, Long>>(); mStatsLock = new Object(); mFileLock = new Object(); mDir = new File(dir); @@ -386,9 +416,9 @@ public final class UsageStatsService extends IUsageStats.Stub { try { long lastResumeTime = Long.parseLong(lastResumeTimeStr); synchronized (mStatsLock) { - Map<String, Long> lrt = mLastResumeTimes.get(pkg); + ArrayMap<String, Long> lrt = mLastResumeTimes.get(pkg); if (lrt == null) { - lrt = new HashMap<String, Long>(); + lrt = new ArrayMap<String, Long>(); mLastResumeTimes.put(pkg, lrt); } lrt.put(comp, lastResumeTime); @@ -591,14 +621,15 @@ public final class UsageStatsService extends IUsageStats.Stub { /** Filter out stats for any packages which aren't present anymore. */ private void filterHistoryStats() { synchronized (mStatsLock) { - // Copy and clear the last resume times map, then copy back stats - // for all installed packages. - Map<String, Map<String, Long>> tmpLastResumeTimes = - new HashMap<String, Map<String, Long>>(mLastResumeTimes); - mLastResumeTimes.clear(); - for (PackageInfo info : mContext.getPackageManager().getInstalledPackages(0)) { - if (tmpLastResumeTimes.containsKey(info.packageName)) { - mLastResumeTimes.put(info.packageName, tmpLastResumeTimes.get(info.packageName)); + IPackageManager pm = AppGlobals.getPackageManager(); + for (int i=0; i<mLastResumeTimes.size(); i++) { + String pkg = mLastResumeTimes.keyAt(i); + try { + if (pm.getPackageUid(pkg, 0) < 0) { + mLastResumeTimes.removeAt(i); + i--; + } + } catch (RemoteException e) { } } } @@ -614,13 +645,14 @@ public final class UsageStatsService extends IUsageStats.Stub { out.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true); out.startTag(null, "usage-history"); synchronized (mStatsLock) { - for (Map.Entry<String, Map<String, Long>> pkgEntry : mLastResumeTimes.entrySet()) { + for (int i=0; i<mLastResumeTimes.size(); i++) { out.startTag(null, "pkg"); - out.attribute(null, "name", pkgEntry.getKey()); - for (Map.Entry<String, Long> compEntry : pkgEntry.getValue().entrySet()) { + out.attribute(null, "name", mLastResumeTimes.keyAt(i)); + ArrayMap<String, Long> comp = mLastResumeTimes.valueAt(i); + for (int j=0; j<comp.size(); j++) { out.startTag(null, "comp"); - out.attribute(null, "name", compEntry.getKey()); - out.attribute(null, "lrt", compEntry.getValue().toString()); + out.attribute(null, "name", comp.keyAt(j)); + out.attribute(null, "lrt", comp.valueAt(j).toString()); out.endTag(null, "comp"); } out.endTag(null, "pkg"); @@ -718,9 +750,9 @@ public final class UsageStatsService extends IUsageStats.Stub { pus.addLaunchCount(mLastResumedComp); } - Map<String, Long> componentResumeTimes = mLastResumeTimes.get(pkgName); + ArrayMap<String, Long> componentResumeTimes = mLastResumeTimes.get(pkgName); if (componentResumeTimes == null) { - componentResumeTimes = new HashMap<String, Long>(); + componentResumeTimes = new ArrayMap<String, Long>(); mLastResumeTimes.put(pkgName, componentResumeTimes); } componentResumeTimes.put(mLastResumedComp, System.currentTimeMillis()); @@ -777,6 +809,25 @@ public final class UsageStatsService extends IUsageStats.Stub { } } + public void noteFullyDrawnTime(ComponentName componentName, int millis) { + enforceCallingPermission(); + String pkgName; + if ((componentName == null) || + ((pkgName = componentName.getPackageName()) == null)) { + return; + } + + // Persist current data to file if needed. + writeStatsToFile(false, false); + + synchronized (mStatsLock) { + PkgUsageStatsExtended pus = mStats.get(pkgName); + if (pus != null) { + pus.addFullyDrawnTime(componentName.getClassName(), millis); + } + } + } + public void enforceCallingPermission() { if (Binder.getCallingPid() == Process.myPid()) { return; @@ -814,9 +865,8 @@ public final class UsageStatsService extends IUsageStats.Stub { return null; } PkgUsageStats retArr[] = new PkgUsageStats[size]; - int i = 0; - for (Map.Entry<String, Map<String, Long>> entry : mLastResumeTimes.entrySet()) { - String pkg = entry.getKey(); + for (int i=0; i<size; i++) { + String pkg = mLastResumeTimes.keyAt(i); long usageTime = 0; int launchCount = 0; @@ -825,8 +875,8 @@ public final class UsageStatsService extends IUsageStats.Stub { usageTime = pus.mUsageTime; launchCount = pus.mLaunchCount; } - retArr[i] = new PkgUsageStats(pkg, launchCount, usageTime, entry.getValue()); - i++; + retArr[i] = new PkgUsageStats(pkg, launchCount, usageTime, + mLastResumeTimes.valueAt(i)); } return retArr; } @@ -866,6 +916,11 @@ public final class UsageStatsService extends IUsageStats.Stub { } File dFile = new File(mDir, file); String dateStr = file.substring(FILE_PREFIX.length()); + if (dateStr.length() > 0 && (dateStr.charAt(0) <= '0' || dateStr.charAt(0) >= '9')) { + // If the remainder does not start with a number, it is not a date, + // so we should ignore it for purposes here. + continue; + } try { Parcel in = getParcelForFile(dFile); collectDumpInfoFromParcelFLOCK(in, pw, dateStr, isCompactOutput, @@ -925,29 +980,33 @@ public final class UsageStatsService extends IUsageStats.Stub { sb.append(','); sb.append(pus.mUsageTime); sb.append('\n'); - final int NC = pus.mLaunchTimes.size(); - if (NC > 0) { - for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) { - sb.append("A:"); - 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); - for (int i=0; i<NUM_LAUNCH_TIME_BINS; i++) { - sb.append(","); - sb.append(times.times[i]); - } - sb.append('\n'); + final int NLT = pus.mLaunchTimes.size(); + for (int i=0; i<NLT; i++) { + sb.append("A:"); + String activity = pus.mLaunchTimes.keyAt(i); + sb.append(activity); + TimeStats times = pus.mLaunchTimes.valueAt(i); + sb.append(','); + sb.append(times.count); + for (int j=0; j<NUM_LAUNCH_TIME_BINS; j++) { + sb.append(","); + sb.append(times.times[j]); + } + sb.append('\n'); + } + final int NFDT = pus.mFullyDrawnTimes.size(); + for (int i=0; i<NFDT; i++) { + sb.append("A:"); + String activity = pus.mFullyDrawnTimes.keyAt(i); + sb.append(activity); + TimeStats times = pus.mFullyDrawnTimes.valueAt(i); + for (int j=0; j<NUM_LAUNCH_TIME_BINS; j++) { + sb.append(","); + sb.append(times.times[j]); } + sb.append('\n'); } - + } else { sb.append(" "); sb.append(pkgName); @@ -957,36 +1016,68 @@ public final class UsageStatsService extends IUsageStats.Stub { sb.append(pus.mUsageTime); sb.append(" ms"); sb.append('\n'); - final int NC = pus.mLaunchTimes.size(); - if (NC > 0) { - for (Map.Entry<String, TimeStats> ent : pus.mLaunchTimes.entrySet()) { - sb.append(" "); - sb.append(ent.getKey()); - TimeStats times = ent.getValue(); - sb.append(": "); - sb.append(times.count); - sb.append(" starts"); - int lastBin = 0; - for (int i=0; i<NUM_LAUNCH_TIME_BINS-1; i++) { - if (times.times[i] != 0) { + final int NLT = pus.mLaunchTimes.size(); + for (int i=0; i<NLT; i++) { + sb.append(" "); + sb.append(pus.mLaunchTimes.keyAt(i)); + TimeStats times = pus.mLaunchTimes.valueAt(i); + sb.append(": "); + sb.append(times.count); + sb.append(" starts"); + int lastBin = 0; + for (int j=0; j<NUM_LAUNCH_TIME_BINS-1; j++) { + if (times.times[j] != 0) { + sb.append(", "); + sb.append(lastBin); + sb.append('-'); + sb.append(LAUNCH_TIME_BINS[j]); + sb.append("ms="); + sb.append(times.times[j]); + } + lastBin = LAUNCH_TIME_BINS[j]; + } + if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) { + sb.append(", "); + sb.append(">="); + sb.append(lastBin); + sb.append("ms="); + sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]); + } + sb.append('\n'); + } + final int NFDT = pus.mFullyDrawnTimes.size(); + for (int i=0; i<NFDT; i++) { + sb.append(" "); + sb.append(pus.mFullyDrawnTimes.keyAt(i)); + TimeStats times = pus.mFullyDrawnTimes.valueAt(i); + sb.append(": fully drawn "); + boolean needComma = false; + int lastBin = 0; + for (int j=0; j<NUM_LAUNCH_TIME_BINS-1; j++) { + if (times.times[j] != 0) { + if (needComma) { sb.append(", "); - sb.append(lastBin); - sb.append('-'); - sb.append(LAUNCH_TIME_BINS[i]); - sb.append("ms="); - sb.append(times.times[i]); + } else { + needComma = true; } - lastBin = LAUNCH_TIME_BINS[i]; - } - if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) { - sb.append(", "); - sb.append(">="); sb.append(lastBin); + sb.append('-'); + sb.append(LAUNCH_TIME_BINS[j]); sb.append("ms="); - sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]); + sb.append(times.times[j]); + } + lastBin = LAUNCH_TIME_BINS[j]; + } + if (times.times[NUM_LAUNCH_TIME_BINS-1] != 0) { + if (needComma) { + sb.append(", "); } - sb.append('\n'); + sb.append(">="); + sb.append(lastBin); + sb.append("ms="); + sb.append(times.times[NUM_LAUNCH_TIME_BINS-1]); } + sb.append('\n'); } } diff --git a/services/java/com/android/server/am/UserStartedState.java b/services/java/com/android/server/am/UserStartedState.java index 0e71f81bb43a..d3e73d5222a4 100644 --- a/services/java/com/android/server/am/UserStartedState.java +++ b/services/java/com/android/server/am/UserStartedState.java @@ -22,7 +22,7 @@ import java.util.ArrayList; import android.app.IStopUserCallback; import android.os.UserHandle; -public class UserStartedState { +public final class UserStartedState { // User is first coming up. public final static int STATE_BOOTING = 0; // User is in the normal running state. diff --git a/services/java/com/android/server/connectivity/DataConnectionStats.java b/services/java/com/android/server/connectivity/DataConnectionStats.java new file mode 100644 index 000000000000..227ab2341012 --- /dev/null +++ b/services/java/com/android/server/connectivity/DataConnectionStats.java @@ -0,0 +1,154 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.connectivity; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.net.ConnectivityManager; +import android.os.RemoteException; +import android.telephony.PhoneStateListener; +import android.telephony.ServiceState; +import android.telephony.SignalStrength; +import android.telephony.TelephonyManager; +import android.util.Log; + +import com.android.internal.app.IBatteryStats; +import com.android.internal.telephony.IccCardConstants; +import com.android.internal.telephony.TelephonyIntents; +import com.android.server.am.BatteryStatsService; + +public class DataConnectionStats extends BroadcastReceiver { + private static final String TAG = "DataConnectionStats"; + private static final boolean DEBUG = false; + + private final Context mContext; + private final IBatteryStats mBatteryStats; + + private IccCardConstants.State mSimState = IccCardConstants.State.READY; + private SignalStrength mSignalStrength; + private ServiceState mServiceState; + private int mDataState = TelephonyManager.DATA_DISCONNECTED; + + public DataConnectionStats(Context context) { + mContext = context; + mBatteryStats = BatteryStatsService.getService(); + } + + public void startMonitoring() { + TelephonyManager phone = + (TelephonyManager)mContext.getSystemService(Context.TELEPHONY_SERVICE); + phone.listen(mPhoneStateListener, + PhoneStateListener.LISTEN_SERVICE_STATE + | PhoneStateListener.LISTEN_SIGNAL_STRENGTHS + | PhoneStateListener.LISTEN_DATA_CONNECTION_STATE + | PhoneStateListener.LISTEN_DATA_ACTIVITY); + + IntentFilter filter = new IntentFilter(); + filter.addAction(TelephonyIntents.ACTION_SIM_STATE_CHANGED); + filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION); + filter.addAction(ConnectivityManager.INET_CONDITION_ACTION); + mContext.registerReceiver(this, filter); + } + + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + if (action.equals(TelephonyIntents.ACTION_SIM_STATE_CHANGED)) { + updateSimState(intent); + notePhoneDataConnectionState(); + } else if (action.equals(ConnectivityManager.CONNECTIVITY_ACTION) || + action.equals(ConnectivityManager.INET_CONDITION_ACTION)) { + notePhoneDataConnectionState(); + } + } + + private void notePhoneDataConnectionState() { + if (mServiceState == null) { + return; + } + boolean simReadyOrUnknown = mSimState == IccCardConstants.State.READY + || mSimState == IccCardConstants.State.UNKNOWN; + boolean visible = (simReadyOrUnknown || isCdma()) // we only check the sim state for GSM + && hasService() + && mDataState == TelephonyManager.DATA_CONNECTED; + int networkType = mServiceState.getDataNetworkType(); + if (DEBUG) Log.d(TAG, String.format("Noting data connection for network type %s: %svisible", + networkType, visible ? "" : "not ")); + try { + mBatteryStats.notePhoneDataConnectionState(networkType, visible); + } catch (RemoteException e) { + Log.w(TAG, "Error noting data connection state", e); + } + } + + private final void updateSimState(Intent intent) { + String stateExtra = intent.getStringExtra(IccCardConstants.INTENT_KEY_ICC_STATE); + if (IccCardConstants.INTENT_VALUE_ICC_ABSENT.equals(stateExtra)) { + mSimState = IccCardConstants.State.ABSENT; + } else if (IccCardConstants.INTENT_VALUE_ICC_READY.equals(stateExtra)) { + mSimState = IccCardConstants.State.READY; + } else if (IccCardConstants.INTENT_VALUE_ICC_LOCKED.equals(stateExtra)) { + final String lockedReason = + intent.getStringExtra(IccCardConstants.INTENT_KEY_LOCKED_REASON); + if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PIN.equals(lockedReason)) { + mSimState = IccCardConstants.State.PIN_REQUIRED; + } else if (IccCardConstants.INTENT_VALUE_LOCKED_ON_PUK.equals(lockedReason)) { + mSimState = IccCardConstants.State.PUK_REQUIRED; + } else { + mSimState = IccCardConstants.State.NETWORK_LOCKED; + } + } else { + mSimState = IccCardConstants.State.UNKNOWN; + } + } + + private boolean isCdma() { + return mSignalStrength != null && !mSignalStrength.isGsm(); + } + + private boolean hasService() { + return mServiceState != null + && mServiceState.getState() != ServiceState.STATE_OUT_OF_SERVICE + && mServiceState.getState() != ServiceState.STATE_POWER_OFF; + } + + private final PhoneStateListener mPhoneStateListener = new PhoneStateListener() { + @Override + public void onSignalStrengthsChanged(SignalStrength signalStrength) { + mSignalStrength = signalStrength; + } + + @Override + public void onServiceStateChanged(ServiceState state) { + mServiceState = state; + notePhoneDataConnectionState(); + } + + @Override + public void onDataConnectionStateChanged(int state, int networkType) { + mDataState = state; + notePhoneDataConnectionState(); + } + + @Override + public void onDataActivity(int direction) { + notePhoneDataConnectionState(); + } + }; +} diff --git a/services/java/com/android/server/connectivity/PacManager.java b/services/java/com/android/server/connectivity/PacManager.java new file mode 100644 index 000000000000..7786fe6e4254 --- /dev/null +++ b/services/java/com/android/server/connectivity/PacManager.java @@ -0,0 +1,381 @@ +/** + * Copyright (c) 2013, The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.android.server.connectivity; + +import android.app.AlarmManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.ServiceConnection; +import android.net.Proxy; +import android.net.ProxyProperties; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.RemoteException; +import android.os.ServiceManager; +import android.os.SystemClock; +import android.os.SystemProperties; +import android.os.UserHandle; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.Log; + +import com.android.internal.annotations.GuardedBy; +import com.android.net.IProxyCallback; +import com.android.net.IProxyPortListener; +import com.android.net.IProxyService; +import com.android.server.IoThread; + +import libcore.io.Streams; + +import java.io.IOException; +import java.net.URL; +import java.net.URLConnection; + +/** + * @hide + */ +public class PacManager { + public static final String PAC_PACKAGE = "com.android.pacprocessor"; + public static final String PAC_SERVICE = "com.android.pacprocessor.PacService"; + public static final String PAC_SERVICE_NAME = "com.android.net.IProxyService"; + + public static final String PROXY_PACKAGE = "com.android.proxyhandler"; + public static final String PROXY_SERVICE = "com.android.proxyhandler.ProxyService"; + + private static final String TAG = "PacManager"; + + private static final String ACTION_PAC_REFRESH = "android.net.proxy.PAC_REFRESH"; + + private static final String DEFAULT_DELAYS = "8 32 120 14400 43200"; + private static final int DELAY_1 = 0; + private static final int DELAY_4 = 3; + private static final int DELAY_LONG = 4; + + /** Keep these values up-to-date with ProxyService.java */ + public static final String KEY_PROXY = "keyProxy"; + private String mCurrentPac; + @GuardedBy("mProxyLock") + private String mPacUrl; + + private AlarmManager mAlarmManager; + @GuardedBy("mProxyLock") + private IProxyService mProxyService; + private PendingIntent mPacRefreshIntent; + private ServiceConnection mConnection; + private ServiceConnection mProxyConnection; + private Context mContext; + + private int mCurrentDelay; + private int mLastPort; + + private boolean mHasSentBroadcast; + private boolean mHasDownloaded; + + private Handler mConnectivityHandler; + private int mProxyMessage; + + /** + * Used for locking when setting mProxyService and all references to mPacUrl or mCurrentPac. + */ + private final Object mProxyLock = new Object(); + + private Runnable mPacDownloader = new Runnable() { + @Override + public void run() { + String file; + synchronized (mProxyLock) { + if (mPacUrl == null) return; + try { + file = get(mPacUrl); + } catch (IOException ioe) { + file = null; + Log.w(TAG, "Failed to load PAC file: " + ioe); + } + } + if (file != null) { + synchronized (mProxyLock) { + if (!file.equals(mCurrentPac)) { + setCurrentProxyScript(file); + } + } + mHasDownloaded = true; + sendProxyIfNeeded(); + longSchedule(); + } else { + reschedule(); + } + } + }; + + class PacRefreshIntentReceiver extends BroadcastReceiver { + public void onReceive(Context context, Intent intent) { + IoThread.getHandler().post(mPacDownloader); + } + } + + public PacManager(Context context, Handler handler, int proxyMessage) { + mContext = context; + mLastPort = -1; + + mPacRefreshIntent = PendingIntent.getBroadcast( + context, 0, new Intent(ACTION_PAC_REFRESH), 0); + context.registerReceiver(new PacRefreshIntentReceiver(), + new IntentFilter(ACTION_PAC_REFRESH)); + mConnectivityHandler = handler; + mProxyMessage = proxyMessage; + } + + private AlarmManager getAlarmManager() { + if (mAlarmManager == null) { + mAlarmManager = (AlarmManager)mContext.getSystemService(Context.ALARM_SERVICE); + } + return mAlarmManager; + } + + /** + * Updates the PAC Manager with current Proxy information. This is called by + * the ConnectivityService directly before a broadcast takes place to allow + * the PacManager to indicate that the broadcast should not be sent and the + * PacManager will trigger a new broadcast when it is ready. + * + * @param proxy Proxy information that is about to be broadcast. + * @return Returns true when the broadcast should not be sent + */ + public synchronized boolean setCurrentProxyScriptUrl(ProxyProperties proxy) { + if (!TextUtils.isEmpty(proxy.getPacFileUrl())) { + if (proxy.getPacFileUrl().equals(mPacUrl) && (proxy.getPort() > 0)) { + // Allow to send broadcast, nothing to do. + return false; + } + synchronized (mProxyLock) { + mPacUrl = proxy.getPacFileUrl(); + } + mCurrentDelay = DELAY_1; + mHasSentBroadcast = false; + mHasDownloaded = false; + getAlarmManager().cancel(mPacRefreshIntent); + bind(); + return true; + } else { + getAlarmManager().cancel(mPacRefreshIntent); + synchronized (mProxyLock) { + mPacUrl = null; + mCurrentPac = null; + if (mProxyService != null) { + try { + mProxyService.stopPacSystem(); + } catch (RemoteException e) { + Log.w(TAG, "Failed to stop PAC service", e); + } finally { + unbind(); + } + } + } + return false; + } + } + + /** + * Does a post and reports back the status code. + * + * @throws IOException + */ + private static String get(String urlString) throws IOException { + URL url = new URL(urlString); + URLConnection urlConnection = url.openConnection(java.net.Proxy.NO_PROXY); + return new String(Streams.readFully(urlConnection.getInputStream())); + } + + private int getNextDelay(int currentDelay) { + if (++currentDelay > DELAY_4) { + return DELAY_4; + } + return currentDelay; + } + + private void longSchedule() { + mCurrentDelay = DELAY_1; + setDownloadIn(DELAY_LONG); + } + + private void reschedule() { + mCurrentDelay = getNextDelay(mCurrentDelay); + setDownloadIn(mCurrentDelay); + } + + private String getPacChangeDelay() { + final ContentResolver cr = mContext.getContentResolver(); + + /** Check system properties for the default value then use secure settings value, if any. */ + String defaultDelay = SystemProperties.get( + "conn." + Settings.Global.PAC_CHANGE_DELAY, + DEFAULT_DELAYS); + String val = Settings.Global.getString(cr, Settings.Global.PAC_CHANGE_DELAY); + return (val == null) ? defaultDelay : val; + } + + private long getDownloadDelay(int delayIndex) { + String[] list = getPacChangeDelay().split(" "); + if (delayIndex < list.length) { + return Long.parseLong(list[delayIndex]); + } + return 0; + } + + private void setDownloadIn(int delayIndex) { + long delay = getDownloadDelay(delayIndex); + long timeTillTrigger = 1000 * delay + SystemClock.elapsedRealtime(); + getAlarmManager().set(AlarmManager.ELAPSED_REALTIME, timeTillTrigger, mPacRefreshIntent); + } + + private boolean setCurrentProxyScript(String script) { + if (mProxyService == null) { + Log.e(TAG, "setCurrentProxyScript: no proxy service"); + return false; + } + try { + mProxyService.setPacFile(script); + mCurrentPac = script; + } catch (RemoteException e) { + Log.e(TAG, "Unable to set PAC file", e); + } + return true; + } + + private void bind() { + if (mContext == null) { + Log.e(TAG, "No context for binding"); + return; + } + Intent intent = new Intent(); + intent.setClassName(PAC_PACKAGE, PAC_SERVICE); + // Already bound no need to bind again. + if ((mProxyConnection != null) && (mConnection != null)) { + if (mLastPort != -1) { + sendPacBroadcast(new ProxyProperties(mPacUrl, mLastPort)); + } else { + Log.e(TAG, "Received invalid port from Local Proxy," + + " PAC will not be operational"); + } + return; + } + mConnection = new ServiceConnection() { + @Override + public void onServiceDisconnected(ComponentName component) { + synchronized (mProxyLock) { + mProxyService = null; + } + } + + @Override + public void onServiceConnected(ComponentName component, IBinder binder) { + synchronized (mProxyLock) { + try { + Log.d(TAG, "Adding service " + PAC_SERVICE_NAME + " " + + binder.getInterfaceDescriptor()); + } catch (RemoteException e1) { + Log.e(TAG, "Remote Exception", e1); + } + ServiceManager.addService(PAC_SERVICE_NAME, binder); + mProxyService = IProxyService.Stub.asInterface(binder); + if (mProxyService == null) { + Log.e(TAG, "No proxy service"); + } else { + try { + mProxyService.startPacSystem(); + } catch (RemoteException e) { + Log.e(TAG, "Unable to reach ProxyService - PAC will not be started", e); + } + IoThread.getHandler().post(mPacDownloader); + } + } + } + }; + mContext.bindService(intent, mConnection, + Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE); + + intent = new Intent(); + intent.setClassName(PROXY_PACKAGE, PROXY_SERVICE); + mProxyConnection = new ServiceConnection() { + @Override + public void onServiceDisconnected(ComponentName component) { + } + + @Override + public void onServiceConnected(ComponentName component, IBinder binder) { + IProxyCallback callbackService = IProxyCallback.Stub.asInterface(binder); + if (callbackService != null) { + try { + callbackService.getProxyPort(new IProxyPortListener.Stub() { + @Override + public void setProxyPort(int port) throws RemoteException { + if (mLastPort != -1) { + // Always need to send if port changed + mHasSentBroadcast = false; + } + mLastPort = port; + if (port != -1) { + Log.d(TAG, "Local proxy is bound on " + port); + sendProxyIfNeeded(); + } else { + Log.e(TAG, "Received invalid port from Local Proxy," + + " PAC will not be operational"); + } + } + }); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + } + }; + mContext.bindService(intent, mProxyConnection, + Context.BIND_AUTO_CREATE | Context.BIND_NOT_FOREGROUND | Context.BIND_NOT_VISIBLE); + } + + private void unbind() { + if (mConnection != null) { + mContext.unbindService(mConnection); + mConnection = null; + } + if (mProxyConnection != null) { + mContext.unbindService(mProxyConnection); + mProxyConnection = null; + } + mProxyService = null; + mLastPort = -1; + } + + private void sendPacBroadcast(ProxyProperties proxy) { + mConnectivityHandler.sendMessage(mConnectivityHandler.obtainMessage(mProxyMessage, proxy)); + } + + private synchronized void sendProxyIfNeeded() { + if (!mHasDownloaded || (mLastPort == -1)) { + return; + } + if (!mHasSentBroadcast) { + sendPacBroadcast(new ProxyProperties(mPacUrl, mLastPort)); + mHasSentBroadcast = true; + } + } +} diff --git a/services/java/com/android/server/connectivity/Tethering.java b/services/java/com/android/server/connectivity/Tethering.java index b83d885fda81..231a40aef67f 100644 --- a/services/java/com/android/server/connectivity/Tethering.java +++ b/services/java/com/android/server/connectivity/Tethering.java @@ -37,13 +37,10 @@ import android.net.NetworkInfo; import android.net.NetworkUtils; import android.net.RouteInfo; import android.os.Binder; -import android.os.HandlerThread; -import android.os.IBinder; import android.os.INetworkManagementService; import android.os.Looper; import android.os.Message; import android.os.RemoteException; -import android.os.ServiceManager; import android.os.UserHandle; import android.provider.Settings; import android.util.Log; @@ -53,6 +50,7 @@ import com.android.internal.telephony.PhoneConstants; import com.android.internal.util.IState; import com.android.internal.util.State; import com.android.internal.util.StateMachine; +import com.android.server.IoThread; import com.google.android.collect.Lists; import java.io.FileDescriptor; @@ -100,7 +98,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { private final INetworkStatsService mStatsService; private final IConnectivityManager mConnService; private Looper mLooper; - private HandlerThread mThread; private HashMap<String, TetherInterfaceSM> mIfaces; // all tethered/tetherable ifaces @@ -147,9 +144,7 @@ public class Tethering extends INetworkManagementEventObserver.Stub { mIfaces = new HashMap<String, TetherInterfaceSM>(); // make our own thread so we don't anr the system - mThread = new HandlerThread("Tethering"); - mThread.start(); - mLooper = mThread.getLooper(); + mLooper = IoThread.get().getLooper(); mTetherMasterSM = new TetherMasterSM("TetherMaster", mLooper); mTetherMasterSM.start(); @@ -320,6 +315,10 @@ public class Tethering extends INetworkManagementEventObserver.Stub { } } + public void addressUpdated(String address, String iface, int flags, int scope) {} + + public void addressRemoved(String address, String iface, int flags, int scope) {} + public void limitReached(String limitName, String iface) {} public void interfaceClassDataActivityChanged(String label, boolean active) {} @@ -689,19 +688,6 @@ public class Tethering extends INetworkManagementEventObserver.Stub { return retVal; } - public String[] getTetheredIfacePairs() { - final ArrayList<String> list = Lists.newArrayList(); - synchronized (mPublicSync) { - for (TetherInterfaceSM sm : mIfaces.values()) { - if (sm.isTethered()) { - list.add(sm.mMyUpstreamIfaceName); - list.add(sm.mIfaceName); - } - } - } - return list.toArray(new String[list.size()]); - } - public String[] getTetherableIfaces() { ArrayList<String> list = new ArrayList<String>(); synchronized (mPublicSync) { diff --git a/services/java/com/android/server/connectivity/Vpn.java b/services/java/com/android/server/connectivity/Vpn.java index 63d39588bb5d..f5a7039a1ab6 100644 --- a/services/java/com/android/server/connectivity/Vpn.java +++ b/services/java/com/android/server/connectivity/Vpn.java @@ -18,6 +18,7 @@ package com.android.server.connectivity; import static android.Manifest.permission.BIND_VPN_SERVICE; +import android.app.AppGlobals; import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; @@ -28,8 +29,10 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.ServiceConnection; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; +import android.content.pm.UserInfo; import android.graphics.Bitmap; import android.graphics.Canvas; import android.graphics.drawable.Drawable; @@ -37,6 +40,7 @@ import android.net.BaseNetworkStateTracker; import android.net.ConnectivityManager; import android.net.IConnectivityManager; import android.net.INetworkManagementEventObserver; +import android.net.LinkAddress; import android.net.LinkProperties; import android.net.LocalSocket; import android.net.LocalSocketAddress; @@ -54,11 +58,14 @@ import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemService; import android.os.UserHandle; +import android.os.UserManager; import android.security.Credentials; import android.security.KeyStore; import android.util.Log; +import android.util.SparseBooleanArray; import android.widget.Toast; +import com.android.internal.annotations.GuardedBy; import com.android.internal.R; import com.android.internal.net.LegacyVpnInfo; import com.android.internal.net.VpnConfig; @@ -74,6 +81,7 @@ import java.net.Inet4Address; import java.net.InetAddress; import java.nio.charset.StandardCharsets; import java.util.Arrays; +import java.util.List; import java.util.concurrent.atomic.AtomicInteger; import libcore.io.IoUtils; @@ -98,20 +106,53 @@ public class Vpn extends BaseNetworkStateTracker { private volatile boolean mEnableNotif = true; private volatile boolean mEnableTeardown = true; private final IConnectivityManager mConnService; + private VpnConfig mConfig; + + /* list of users using this VPN. */ + @GuardedBy("this") + private SparseBooleanArray mVpnUsers = null; + private BroadcastReceiver mUserIntentReceiver = null; + + private final int mUserId; public Vpn(Context context, VpnCallback callback, INetworkManagementService netService, - IConnectivityManager connService) { + IConnectivityManager connService, int userId) { // TODO: create dedicated TYPE_VPN network type super(ConnectivityManager.TYPE_DUMMY); mContext = context; mCallback = callback; mConnService = connService; + mUserId = userId; try { netService.registerObserver(mObserver); } catch (RemoteException e) { Log.wtf(TAG, "Problem registering observer", e); } + if (userId == UserHandle.USER_OWNER) { + // Owner's VPN also needs to handle restricted users + mUserIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + final String action = intent.getAction(); + final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, + UserHandle.USER_NULL); + if (userId == UserHandle.USER_NULL) return; + + if (Intent.ACTION_USER_ADDED.equals(action)) { + onUserAdded(userId); + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + onUserRemoved(userId); + } + } + }; + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_USER_ADDED); + intentFilter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiverAsUser( + mUserIntentReceiver, UserHandle.ALL, intentFilter, null, null); + } } /** @@ -197,15 +238,25 @@ public class Vpn extends BaseNetworkStateTracker { // Reset the interface and hide the notification. if (mInterface != null) { - jniReset(mInterface); final long token = Binder.clearCallingIdentity(); try { mCallback.restore(); - hideNotification(); + final int size = mVpnUsers.size(); + final boolean forwardDns = (mConfig.dnsServers != null && + mConfig.dnsServers.size() != 0); + for (int i = 0; i < size; i++) { + int user = mVpnUsers.keyAt(i); + mCallback.clearUserForwarding(mInterface, user, forwardDns); + hideNotification(user); + } + + mCallback.clearMarkedForwarding(mInterface); } finally { Binder.restoreCallingIdentity(token); } + jniReset(mInterface); mInterface = null; + mVpnUsers = null; } // Revoke the connection or stop LegacyVpnRunner. @@ -225,6 +276,7 @@ public class Vpn extends BaseNetworkStateTracker { Log.i(TAG, "Switched from " + mPackage + " to " + newPackage); mPackage = newPackage; + mConfig = null; updateState(DetailedState.IDLE, "prepare"); return true; } @@ -237,12 +289,22 @@ public class Vpn extends BaseNetworkStateTracker { * @param interfaze The name of the interface. */ public void protect(ParcelFileDescriptor socket, String interfaze) throws Exception { + PackageManager pm = mContext.getPackageManager(); - ApplicationInfo app = pm.getApplicationInfo(mPackage, 0); - if (Binder.getCallingUid() != app.uid) { + int appUid = pm.getPackageUid(mPackage, mUserId); + if (Binder.getCallingUid() != appUid) { throw new SecurityException("Unauthorized Caller"); } + // protect the socket from routing rules + final long token = Binder.clearCallingIdentity(); + try { + mCallback.protect(socket); + } finally { + Binder.restoreCallingIdentity(token); + } + // bind the socket to the interface jniProtect(socket.getFd(), interfaze); + } /** @@ -255,44 +317,40 @@ public class Vpn extends BaseNetworkStateTracker { */ public synchronized ParcelFileDescriptor establish(VpnConfig config) { // Check if the caller is already prepared. + UserManager mgr = UserManager.get(mContext); PackageManager pm = mContext.getPackageManager(); ApplicationInfo app = null; try { - app = pm.getApplicationInfo(mPackage, 0); + app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId); + if (Binder.getCallingUid() != app.uid) { + return null; + } } catch (Exception e) { return null; } - if (Binder.getCallingUid() != app.uid) { - return null; - } - // Check if the service is properly declared. Intent intent = new Intent(VpnConfig.SERVICE_INTERFACE); intent.setClassName(mPackage, config.user); - ResolveInfo info = pm.resolveService(intent, 0); - if (info == null) { - throw new SecurityException("Cannot find " + config.user); - } - if (!BIND_VPN_SERVICE.equals(info.serviceInfo.permission)) { - throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE); - } - - // Load the label. - String label = app.loadLabel(pm).toString(); + long token = Binder.clearCallingIdentity(); + try { + // Restricted users are not allowed to create VPNs, they are tied to Owner + UserInfo user = mgr.getUserInfo(mUserId); + if (user.isRestricted()) { + throw new SecurityException("Restricted users cannot establish VPNs"); + } - // Load the icon and convert it into a bitmap. - Drawable icon = app.loadIcon(pm); - Bitmap bitmap = null; - if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) { - int width = mContext.getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_width); - int height = mContext.getResources().getDimensionPixelSize( - android.R.dimen.notification_large_icon_height); - icon.setBounds(0, 0, width, height); - bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); - Canvas c = new Canvas(bitmap); - icon.draw(c); - c.setBitmap(null); + ResolveInfo info = AppGlobals.getPackageManager().resolveService(intent, + null, 0, mUserId); + if (info == null) { + throw new SecurityException("Cannot find " + config.user); + } + if (!BIND_VPN_SERVICE.equals(info.serviceInfo.permission)) { + throw new SecurityException(config.user + " does not require " + BIND_VPN_SERVICE); + } + } catch (RemoteException e) { + throw new SecurityException("Cannot find " + config.user); + } finally { + Binder.restoreCallingIdentity(token); } // Configure the interface. Abort if any of these steps fails. @@ -300,14 +358,18 @@ public class Vpn extends BaseNetworkStateTracker { try { updateState(DetailedState.CONNECTING, "establish"); String interfaze = jniGetName(tun.getFd()); - if (jniSetAddresses(interfaze, config.addresses) < 1) { - throw new IllegalArgumentException("At least one address must be specified"); + + // TEMP use the old jni calls until there is support for netd address setting + StringBuilder builder = new StringBuilder(); + for (LinkAddress address : config.addresses) { + builder.append(" " + address); } - if (config.routes != null) { - jniSetRoutes(interfaze, config.routes); + if (jniSetAddresses(interfaze, builder.toString()) < 1) { + throw new IllegalArgumentException("At least one address must be specified"); } Connection connection = new Connection(); - if (!mContext.bindService(intent, connection, Context.BIND_AUTO_CREATE)) { + if (!mContext.bindServiceAsUser(intent, connection, Context.BIND_AUTO_CREATE, + new UserHandle(mUserId))) { throw new IllegalStateException("Cannot bind " + config.user); } if (mConnection != null) { @@ -318,30 +380,161 @@ public class Vpn extends BaseNetworkStateTracker { } mConnection = connection; mInterface = interfaze; + + // Fill more values. + config.user = mPackage; + config.interfaze = mInterface; + config.startTime = SystemClock.elapsedRealtime(); + mConfig = config; + // Set up forwarding and DNS rules. + mVpnUsers = new SparseBooleanArray(); + token = Binder.clearCallingIdentity(); + try { + mCallback.setMarkedForwarding(mInterface); + mCallback.setRoutes(interfaze, config.routes); + mCallback.override(mInterface, config.dnsServers, config.searchDomains); + addVpnUserLocked(mUserId); + + } finally { + Binder.restoreCallingIdentity(token); + } + } catch (RuntimeException e) { updateState(DetailedState.FAILED, "establish"); IoUtils.closeQuietly(tun); + // make sure marked forwarding is cleared if it was set + try { + mCallback.clearMarkedForwarding(mInterface); + } catch (Exception ingored) { + // ignored + } throw e; } Log.i(TAG, "Established by " + config.user + " on " + mInterface); - // Fill more values. - config.user = mPackage; - config.interfaze = mInterface; - // Override DNS servers and show the notification. - final long token = Binder.clearCallingIdentity(); - try { - mCallback.override(config.dnsServers, config.searchDomains); - showNotification(config, label, bitmap); - } finally { - Binder.restoreCallingIdentity(token); + // If we are owner assign all Restricted Users to this VPN + if (mUserId == UserHandle.USER_OWNER) { + token = Binder.clearCallingIdentity(); + try { + for (UserInfo user : mgr.getUsers()) { + if (user.isRestricted()) { + try { + addVpnUserLocked(user.id); + } catch (Exception e) { + Log.wtf(TAG, "Failed to add user " + user.id + " to owner's VPN"); + } + } + } + } finally { + Binder.restoreCallingIdentity(token); + } } // TODO: ensure that contract class eventually marks as connected updateState(DetailedState.AUTHENTICATING, "establish"); return tun; } + private boolean isRunningLocked() { + return mVpnUsers != null; + } + + private void addVpnUserLocked(int user) { + enforceControlPermission(); + + if (!isRunningLocked()) { + throw new IllegalStateException("VPN is not active"); + } + + final boolean forwardDns = (mConfig.dnsServers != null && + mConfig.dnsServers.size() != 0); + + // add the user + mCallback.addUserForwarding(mInterface, user, forwardDns); + mVpnUsers.put(user, true); + + // show the notification + if (!mPackage.equals(VpnConfig.LEGACY_VPN)) { + // Load everything for the user's notification + PackageManager pm = mContext.getPackageManager(); + ApplicationInfo app = null; + try { + app = AppGlobals.getPackageManager().getApplicationInfo(mPackage, 0, mUserId); + } catch (RemoteException e) { + throw new IllegalStateException("Invalid application"); + } + String label = app.loadLabel(pm).toString(); + // Load the icon and convert it into a bitmap. + Drawable icon = app.loadIcon(pm); + Bitmap bitmap = null; + if (icon.getIntrinsicWidth() > 0 && icon.getIntrinsicHeight() > 0) { + int width = mContext.getResources().getDimensionPixelSize( + android.R.dimen.notification_large_icon_width); + int height = mContext.getResources().getDimensionPixelSize( + android.R.dimen.notification_large_icon_height); + icon.setBounds(0, 0, width, height); + bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + Canvas c = new Canvas(bitmap); + icon.draw(c); + c.setBitmap(null); + } + showNotification(label, bitmap, user); + } else { + showNotification(null, null, user); + } + } + + private void removeVpnUserLocked(int user) { + enforceControlPermission(); + + if (!isRunningLocked()) { + throw new IllegalStateException("VPN is not active"); + } + final boolean forwardDns = (mConfig.dnsServers != null && + mConfig.dnsServers.size() != 0); + mCallback.clearUserForwarding(mInterface, user, forwardDns); + mVpnUsers.delete(user); + hideNotification(user); + } + + private void onUserAdded(int userId) { + // If the user is restricted tie them to the owner's VPN + synchronized(Vpn.this) { + UserManager mgr = UserManager.get(mContext); + UserInfo user = mgr.getUserInfo(userId); + if (user.isRestricted()) { + try { + addVpnUserLocked(userId); + } catch (Exception e) { + Log.wtf(TAG, "Failed to add restricted user to owner", e); + } + } + } + } + + private void onUserRemoved(int userId) { + // clean up if restricted + synchronized(Vpn.this) { + UserManager mgr = UserManager.get(mContext); + UserInfo user = mgr.getUserInfo(userId); + if (user.isRestricted()) { + try { + removeVpnUserLocked(userId); + } catch (Exception e) { + Log.wtf(TAG, "Failed to remove restricted user to owner", e); + } + } + } + } + + /** + * Return the configuration of the currently running VPN. + */ + public VpnConfig getVpnConfig() { + enforceControlPermission(); + return mConfig; + } + @Deprecated public synchronized void interfaceStatusChanged(String iface, boolean up) { try { @@ -367,8 +560,18 @@ public class Vpn extends BaseNetworkStateTracker { if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) { final long token = Binder.clearCallingIdentity(); try { + final int size = mVpnUsers.size(); + final boolean forwardDns = (mConfig.dnsServers != null && + mConfig.dnsServers.size() != 0); + for (int i = 0; i < size; i++) { + int user = mVpnUsers.keyAt(i); + mCallback.clearUserForwarding(mInterface, user, forwardDns); + hideNotification(user); + } + mVpnUsers = null; + mCallback.clearMarkedForwarding(mInterface); + mCallback.restore(); - hideNotification(); } finally { Binder.restoreCallingIdentity(token); } @@ -391,16 +594,19 @@ public class Vpn extends BaseNetworkStateTracker { if (Binder.getCallingUid() == Process.SYSTEM_UID) { return; } - + int appId = UserHandle.getAppId(Binder.getCallingUid()); + final long token = Binder.clearCallingIdentity(); try { // System dialogs are also allowed to control VPN. PackageManager pm = mContext.getPackageManager(); ApplicationInfo app = pm.getApplicationInfo(VpnConfig.DIALOGS_PACKAGE, 0); - if (Binder.getCallingUid() == app.uid) { + if (appId == app.uid) { return; } } catch (Exception e) { // ignore + } finally { + Binder.restoreCallingIdentity(token); } throw new SecurityException("Unauthorized Caller"); @@ -420,9 +626,9 @@ public class Vpn extends BaseNetworkStateTracker { } } - private void showNotification(VpnConfig config, String label, Bitmap icon) { + private void showNotification(String label, Bitmap icon, int user) { if (!mEnableNotif) return; - mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext, config); + mStatusIntent = VpnConfig.getIntentForStatusPanel(mContext); NotificationManager nm = (NotificationManager) mContext.getSystemService(Context.NOTIFICATION_SERVICE); @@ -430,9 +636,8 @@ public class Vpn extends BaseNetworkStateTracker { if (nm != null) { String title = (label == null) ? mContext.getString(R.string.vpn_title) : mContext.getString(R.string.vpn_title_long, label); - String text = (config.session == null) ? mContext.getString(R.string.vpn_text) : - mContext.getString(R.string.vpn_text_long, config.session); - config.startTime = SystemClock.elapsedRealtime(); + String text = (mConfig.session == null) ? mContext.getString(R.string.vpn_text) : + mContext.getString(R.string.vpn_text_long, mConfig.session); Notification notification = new Notification.Builder(mContext) .setSmallIcon(R.drawable.vpn_connected) @@ -443,11 +648,11 @@ public class Vpn extends BaseNetworkStateTracker { .setDefaults(0) .setOngoing(true) .build(); - nm.notifyAsUser(null, R.drawable.vpn_connected, notification, UserHandle.ALL); + nm.notifyAsUser(null, R.drawable.vpn_connected, notification, new UserHandle(user)); } } - private void hideNotification() { + private void hideNotification(int user) { if (!mEnableNotif) return; mStatusIntent = null; @@ -455,7 +660,7 @@ public class Vpn extends BaseNetworkStateTracker { mContext.getSystemService(Context.NOTIFICATION_SERVICE); if (nm != null) { - nm.cancelAsUser(null, R.drawable.vpn_connected, UserHandle.ALL); + nm.cancelAsUser(null, R.drawable.vpn_connected, new UserHandle(user)); } } @@ -577,7 +782,8 @@ public class Vpn extends BaseNetworkStateTracker { config.user = profile.key; config.interfaze = iface; config.session = profile.name; - config.routes = profile.routes; + + config.addLegacyRoutes(profile.routes); if (!profile.dnsServers.isEmpty()) { config.dnsServers = Arrays.asList(profile.dnsServers.split(" +")); } @@ -620,7 +826,7 @@ public class Vpn extends BaseNetworkStateTracker { if (mLegacyVpnRunner == null) return null; final LegacyVpnInfo info = new LegacyVpnInfo(); - info.key = mLegacyVpnRunner.mConfig.user; + info.key = mConfig.user; info.state = LegacyVpnInfo.stateFromNetworkInfo(mNetworkInfo); if (mNetworkInfo.isConnected()) { info.intent = mStatusIntent; @@ -630,7 +836,7 @@ public class Vpn extends BaseNetworkStateTracker { public VpnConfig getLegacyVpnConfig() { if (mLegacyVpnRunner != null) { - return mLegacyVpnRunner.mConfig; + return mConfig; } else { return null; } @@ -646,7 +852,6 @@ public class Vpn extends BaseNetworkStateTracker { private class LegacyVpnRunner extends Thread { private static final String TAG = "LegacyVpnRunner"; - private final VpnConfig mConfig; private final String[] mDaemons; private final String[][] mArguments; private final LocalSocket[] mSockets; @@ -691,7 +896,7 @@ public class Vpn extends BaseNetworkStateTracker { // mConfig.interfaze will change to point to OUR // internal interface soon. TODO - add inner/outer to mconfig // TODO - we have a race - if the outer iface goes away/disconnects before we hit this - // we will leave the VPN up. We should check that it's still there/connected after + // we will leave the VPN up. We should check that it's still there/connected after // registering mOuterInterface = mConfig.interfaze; @@ -867,11 +1072,11 @@ public class Vpn extends BaseNetworkStateTracker { // Set the interface and the addresses in the config. mConfig.interfaze = parameters[0].trim(); - mConfig.addresses = parameters[1].trim(); + mConfig.addLegacyAddresses(parameters[1]); // Set the routes if they are not set in the config. if (mConfig.routes == null || mConfig.routes.isEmpty()) { - mConfig.routes = parameters[2].trim(); + mConfig.addLegacyRoutes(parameters[2]); } // Set the DNS servers if they are not set in the config. @@ -891,10 +1096,19 @@ public class Vpn extends BaseNetworkStateTracker { } // Set the routes. - jniSetRoutes(mConfig.interfaze, mConfig.routes); + long token = Binder.clearCallingIdentity(); + try { + mCallback.setMarkedForwarding(mConfig.interfaze); + mCallback.setRoutes(mConfig.interfaze, mConfig.routes); + } finally { + Binder.restoreCallingIdentity(token); + } // Here is the last step and it must be done synchronously. synchronized (Vpn.this) { + // Set the start time + mConfig.startTime = SystemClock.elapsedRealtime(); + // Check if the thread is interrupted while we are waiting. checkpoint(false); @@ -905,14 +1119,44 @@ public class Vpn extends BaseNetworkStateTracker { // Now INetworkManagementEventObserver is watching our back. mInterface = mConfig.interfaze; - mCallback.override(mConfig.dnsServers, mConfig.searchDomains); - showNotification(mConfig, null, null); + mVpnUsers = new SparseBooleanArray(); + + token = Binder.clearCallingIdentity(); + try { + mCallback.override(mInterface, mConfig.dnsServers, mConfig.searchDomains); + addVpnUserLocked(mUserId); + } finally { + Binder.restoreCallingIdentity(token); + } + // Assign all restircted users to this VPN + // (Legacy VPNs are Owner only) + UserManager mgr = UserManager.get(mContext); + token = Binder.clearCallingIdentity(); + try { + for (UserInfo user : mgr.getUsers()) { + if (user.isRestricted()) { + try { + addVpnUserLocked(user.id); + } catch (Exception e) { + Log.wtf(TAG, "Failed to add user " + user.id + + " to owner's VPN"); + } + } + } + } finally { + Binder.restoreCallingIdentity(token); + } Log.i(TAG, "Connected!"); updateState(DetailedState.CONNECTED, "execute"); } } catch (Exception e) { Log.i(TAG, "Aborting", e); + // make sure the routing is cleared + try { + mCallback.clearMarkedForwarding(mConfig.interfaze); + } catch (Exception ignored) { + } exit(); } finally { // Kill the daemons if they fail to stop. diff --git a/services/java/com/android/server/content/ContentService.java b/services/java/com/android/server/content/ContentService.java index f82cf0110227..cb35ef11c6f6 100644 --- a/services/java/com/android/server/content/ContentService.java +++ b/services/java/com/android/server/content/ContentService.java @@ -26,6 +26,7 @@ import android.content.ISyncStatusObserver; import android.content.PeriodicSync; import android.content.SyncAdapterType; import android.content.SyncInfo; +import android.content.SyncRequest; import android.content.SyncStatusInfo; import android.database.IContentObserver; import android.database.sqlite.SQLiteException; @@ -36,8 +37,11 @@ import android.os.IBinder; import android.os.Parcel; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.SystemProperties; import android.os.UserHandle; +import android.text.TextUtils; import android.util.Log; +import android.util.Pair; import android.util.Slog; import android.util.SparseIntArray; @@ -61,6 +65,10 @@ public final class ContentService extends IContentService.Stub { private final Object mSyncManagerLock = new Object(); private SyncManager getSyncManager() { + if (SystemProperties.getBoolean("config.disable_network", false)) { + return null; + } + synchronized(mSyncManagerLock) { try { // Try to create the SyncManager, return null if it fails (e.g. the disk is full). @@ -134,7 +142,7 @@ public final class ContentService extends IContentService.Stub { // The content service only throws security exceptions, so let's // log all others. if (!(e instanceof SecurityException)) { - Log.e(TAG, "Content Service Crash", e); + Slog.wtf(TAG, "Content Service Crash", e); } throw e; } @@ -307,6 +315,7 @@ public final class ContentService extends IContentService.Stub { } } + @Override public void requestSync(Account account, String authority, Bundle extras) { ContentResolver.validateSyncExtrasBundle(extras); int userId = UserHandle.getCallingUserId(); @@ -318,7 +327,8 @@ public final class ContentService extends IContentService.Stub { try { SyncManager syncManager = getSyncManager(); if (syncManager != null) { - syncManager.scheduleSync(account, userId, uId, authority, extras, 0 /* no delay */, + syncManager.scheduleSync(account, userId, uId, authority, extras, + 0 /* no delay */, 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */); } } finally { @@ -327,14 +337,74 @@ public final class ContentService extends IContentService.Stub { } /** + * Request a sync with a generic {@link android.content.SyncRequest} object. This will be + * either: + * periodic OR one-off sync. + * and + * anonymous OR provider sync. + * Depending on the request, we enqueue to suit in the SyncManager. + * @param request The request object. Validation of this object is done by its builder. + */ + @Override + public void sync(SyncRequest request) { + Bundle extras = request.getBundle(); + long flextime = request.getSyncFlexTime(); + long runAtTime = request.getSyncRunTime(); + int userId = UserHandle.getCallingUserId(); + int uId = Binder.getCallingUid(); + + // This makes it so that future permission checks will be in the context of this + // process rather than the caller's process. We will restore this before returning. + long identityToken = clearCallingIdentity(); + try { + SyncManager syncManager = getSyncManager(); + if (syncManager != null) { + if (request.hasAuthority()) { + // Sync Adapter registered with the system - old API. + final Account account = request.getAccount(); + final String provider = request.getProvider(); + if (request.isPeriodic()) { + mContext.enforceCallingOrSelfPermission( + Manifest.permission.WRITE_SYNC_SETTINGS, + "no permission to write the sync settings"); + if (runAtTime < 60) { + Slog.w(TAG, "Requested poll frequency of " + runAtTime + + " seconds being rounded up to 60 seconds."); + runAtTime = 60; + } + PeriodicSync syncToAdd = + new PeriodicSync(account, provider, extras, runAtTime, flextime); + getSyncManager().getSyncStorageEngine().addPeriodicSync(syncToAdd, userId); + } else { + long beforeRuntimeMillis = (flextime) * 1000; + long runtimeMillis = runAtTime * 1000; + syncManager.scheduleSync( + account, userId, uId, provider, extras, + beforeRuntimeMillis, runtimeMillis, + false /* onlyThoseWithUnknownSyncableState */); + } + } else { + Log.w(TAG, "Unrecognised sync parameters, doing nothing."); + } + } + } finally { + restoreCallingIdentity(identityToken); + } + } + + /** * Clear all scheduled sync operations that match the uri and cancel the active sync * if they match the authority and account, if they are present. * @param account filter the pending and active syncs to cancel using this account * @param authority filter the pending and active syncs to cancel using this authority */ + @Override public void cancelSync(Account account, String authority) { - int userId = UserHandle.getCallingUserId(); + if (authority != null && authority.length() == 0) { + throw new IllegalArgumentException("Authority must be non-empty"); + } + int userId = UserHandle.getCallingUserId(); // This makes it so that future permission checks will be in the context of this // process rather than the caller's process. We will restore this before returning. long identityToken = clearCallingIdentity(); @@ -353,6 +423,7 @@ public final class ContentService extends IContentService.Stub { * Get information about the SyncAdapters that are known to the system. * @return an array of SyncAdapters that have registered with the system */ + @Override public SyncAdapterType[] getSyncAdapterTypes() { // This makes it so that future permission checks will be in the context of this // process rather than the caller's process. We will restore this before returning. @@ -366,11 +437,12 @@ public final class ContentService extends IContentService.Stub { } } + @Override public boolean getSyncAutomatically(Account account, String providerName) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, "no permission to read the sync settings"); - int userId = UserHandle.getCallingUserId(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); @@ -384,11 +456,15 @@ public final class ContentService extends IContentService.Stub { return false; } + @Override public void setSyncAutomatically(Account account, String providerName, boolean sync) { + if (TextUtils.isEmpty(providerName)) { + throw new IllegalArgumentException("Authority must be non-empty"); + } mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, "no permission to write the sync settings"); - int userId = UserHandle.getCallingUserId(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); @@ -401,12 +477,20 @@ public final class ContentService extends IContentService.Stub { } } + /** Old API. Schedule periodic sync with default flex time. */ + @Override public void addPeriodicSync(Account account, String authority, Bundle extras, long pollFrequency) { + if (account == null) { + throw new IllegalArgumentException("Account must not be null"); + } + if (TextUtils.isEmpty(authority)) { + throw new IllegalArgumentException("Authority must not be empty."); + } mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, "no permission to write the sync settings"); - int userId = UserHandle.getCallingUserId(); + int userId = UserHandle.getCallingUserId(); if (pollFrequency < 60) { Slog.w(TAG, "Requested poll frequency of " + pollFrequency + " seconds being rounded up to 60 seconds."); @@ -415,32 +499,59 @@ public final class ContentService extends IContentService.Stub { long identityToken = clearCallingIdentity(); try { - getSyncManager().getSyncStorageEngine().addPeriodicSync( - account, userId, authority, extras, pollFrequency); + // Add default flex time to this sync. + PeriodicSync syncToAdd = + new PeriodicSync(account, authority, extras, + pollFrequency, + SyncStorageEngine.calculateDefaultFlexTime(pollFrequency)); + getSyncManager().getSyncStorageEngine().addPeriodicSync(syncToAdd, userId); } finally { restoreCallingIdentity(identityToken); } } + @Override public void removePeriodicSync(Account account, String authority, Bundle extras) { + if (account == null) { + throw new IllegalArgumentException("Account must not be null"); + } + if (TextUtils.isEmpty(authority)) { + throw new IllegalArgumentException("Authority must not be empty"); + } mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, "no permission to write the sync settings"); - int userId = UserHandle.getCallingUserId(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { - getSyncManager().getSyncStorageEngine().removePeriodicSync(account, userId, authority, - extras); + PeriodicSync syncToRemove = new PeriodicSync(account, authority, extras, + 0 /* Not read for removal */, 0 /* Not read for removal */); + getSyncManager().getSyncStorageEngine().removePeriodicSync(syncToRemove, userId); } finally { restoreCallingIdentity(identityToken); } } + /** + * TODO: Implement. + * @param request Sync to remove. + */ + public void removeSync(SyncRequest request) { + + } + + @Override public List<PeriodicSync> getPeriodicSyncs(Account account, String providerName) { + if (account == null) { + throw new IllegalArgumentException("Account must not be null"); + } + if (TextUtils.isEmpty(providerName)) { + throw new IllegalArgumentException("Authority must not be empty"); + } mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, "no permission to read the sync settings"); - int userId = UserHandle.getCallingUserId(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { return getSyncManager().getSyncStorageEngine().getPeriodicSyncs( @@ -468,11 +579,15 @@ public final class ContentService extends IContentService.Stub { return -1; } + @Override public void setIsSyncable(Account account, String providerName, int syncable) { + if (TextUtils.isEmpty(providerName)) { + throw new IllegalArgumentException("Authority must not be empty"); + } mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, "no permission to write the sync settings"); - int userId = UserHandle.getCallingUserId(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); @@ -485,11 +600,12 @@ public final class ContentService extends IContentService.Stub { } } + @Override public boolean getMasterSyncAutomatically() { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_SETTINGS, "no permission to read the sync settings"); - int userId = UserHandle.getCallingUserId(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); @@ -502,11 +618,12 @@ public final class ContentService extends IContentService.Stub { return false; } + @Override public void setMasterSyncAutomatically(boolean flag) { mContext.enforceCallingOrSelfPermission(Manifest.permission.WRITE_SYNC_SETTINGS, "no permission to write the sync settings"); - int userId = UserHandle.getCallingUserId(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); @@ -539,8 +656,8 @@ public final class ContentService extends IContentService.Stub { public List<SyncInfo> getCurrentSyncs() { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, "no permission to read the sync stats"); - int userId = UserHandle.getCallingUserId(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { return getSyncManager().getSyncStorageEngine().getCurrentSyncs(userId); @@ -550,10 +667,13 @@ public final class ContentService extends IContentService.Stub { } public SyncStatusInfo getSyncStatus(Account account, String authority) { + if (TextUtils.isEmpty(authority)) { + throw new IllegalArgumentException("Authority must not be empty"); + } mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, "no permission to read the sync stats"); - int userId = UserHandle.getCallingUserId(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); @@ -570,8 +690,8 @@ public final class ContentService extends IContentService.Stub { public boolean isSyncPending(Account account, String authority) { mContext.enforceCallingOrSelfPermission(Manifest.permission.READ_SYNC_STATS, "no permission to read the sync stats"); - int userId = UserHandle.getCallingUserId(); + int userId = UserHandle.getCallingUserId(); long identityToken = clearCallingIdentity(); try { SyncManager syncManager = getSyncManager(); diff --git a/services/java/com/android/server/content/SyncManager.java b/services/java/com/android/server/content/SyncManager.java index ff1281e5c546..71d8d9911f9a 100644 --- a/services/java/com/android/server/content/SyncManager.java +++ b/services/java/com/android/server/content/SyncManager.java @@ -34,6 +34,7 @@ import android.content.ISyncContext; import android.content.ISyncStatusObserver; import android.content.Intent; import android.content.IntentFilter; +import android.content.PeriodicSync; import android.content.ServiceConnection; import android.content.SyncActivityTooManyDeletes; import android.content.SyncAdapterType; @@ -53,12 +54,10 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.os.Bundle; import android.os.Handler; -import android.os.HandlerThread; import android.os.IBinder; import android.os.Looper; import android.os.Message; import android.os.PowerManager; -import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; @@ -68,24 +67,28 @@ import android.os.WorkSource; import android.provider.Settings; import android.text.format.DateUtils; import android.text.format.Time; +import android.text.TextUtils; import android.util.EventLog; import android.util.Log; import android.util.Pair; import com.android.internal.R; import com.android.internal.annotations.GuardedBy; +import com.android.internal.os.BackgroundThread; import com.android.internal.util.IndentingPrintWriter; import com.android.server.accounts.AccountManagerService; +import com.android.server.content.SyncStorageEngine.AuthorityInfo; import com.android.server.content.SyncStorageEngine.OnSyncRequestListener; import com.google.android.collect.Lists; import com.google.android.collect.Maps; import com.google.android.collect.Sets; import java.io.FileDescriptor; -import java.io.PrintStream; import java.io.PrintWriter; +import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; +import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -114,7 +117,7 @@ public class SyncManager { private static final long MAX_TIME_PER_SYNC; static { - final boolean isLargeRAM = ActivityManager.isLargeRAM(); + final boolean isLargeRAM = !ActivityManager.isLowRamDeviceStatic(); int defaultMaxInitSyncs = isLargeRAM ? 5 : 2; int defaultMaxRegularSyncs = isLargeRAM ? 2 : 1; MAX_SIMULTANEOUS_INITIALIZATION_SYNCS = @@ -191,6 +194,7 @@ public class SyncManager { private BroadcastReceiver mStorageIntentReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { String action = intent.getAction(); if (Intent.ACTION_DEVICE_STORAGE_LOW.equals(action)) { @@ -211,36 +215,39 @@ public class SyncManager { }; private BroadcastReceiver mBootCompletedReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { mSyncHandler.onBootCompleted(); } }; private BroadcastReceiver mBackgroundDataSettingChanged = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { if (getConnectivityManager().getBackgroundDataSetting()) { scheduleSync(null /* account */, UserHandle.USER_ALL, SyncOperation.REASON_BACKGROUND_DATA_SETTINGS_CHANGED, null /* authority */, - new Bundle(), 0 /* delay */, + new Bundle(), 0 /* delay */, 0 /* delay */, false /* onlyThoseWithUnknownSyncableState */); } } }; private BroadcastReceiver mAccountsUpdatedReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { updateRunningAccounts(); // Kick off sync for everyone, since this was a radical account change scheduleSync(null, UserHandle.USER_ALL, SyncOperation.REASON_ACCOUNTS_UPDATED, null, - null, 0 /* no delay */, false); + null, 0 /* no delay */, 0/* no delay */, false); } }; private final PowerManager mPowerManager; - // Use this as a random offset to seed all periodic syncs + // Use this as a random offset to seed all periodic syncs. private int mSyncRandomOffsetMillis; private final UserManager mUserManager; @@ -297,6 +304,7 @@ public class SyncManager { private BroadcastReceiver mConnectivityIntentReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { final boolean wasConnected = mDataConnectionIsConnected; @@ -308,7 +316,9 @@ public class SyncManager { if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.v(TAG, "Reconnection detected: clearing all backoffs"); } - mSyncStorageEngine.clearAllBackoffs(mSyncQueue); + synchronized(mSyncQueue) { + mSyncStorageEngine.clearAllBackoffsLocked(mSyncQueue); + } } sendCheckAlarmsMessage(); } @@ -322,6 +332,7 @@ public class SyncManager { private BroadcastReceiver mShutdownIntentReceiver = new BroadcastReceiver() { + @Override public void onReceive(Context context, Intent intent) { Log.w(TAG, "Writing sync state before shutdown..."); getSyncStorageEngine().writeAllState(); @@ -372,19 +383,20 @@ public class SyncManager { SyncStorageEngine.init(context); mSyncStorageEngine = SyncStorageEngine.getSingleton(); mSyncStorageEngine.setOnSyncRequestListener(new OnSyncRequestListener() { + @Override public void onSyncRequest(Account account, int userId, int reason, String authority, Bundle extras) { - scheduleSync(account, userId, reason, authority, extras, 0, false); + scheduleSync(account, userId, reason, authority, extras, + 0 /* no delay */, + 0 /* no delay */, + false); } }); mSyncAdapters = new SyncAdaptersCache(mContext); mSyncQueue = new SyncQueue(mContext.getPackageManager(), mSyncStorageEngine, mSyncAdapters); - HandlerThread syncThread = new HandlerThread("SyncHandlerThread", - Process.THREAD_PRIORITY_BACKGROUND); - syncThread.start(); - mSyncHandler = new SyncHandler(syncThread.getLooper()); + mSyncHandler = new SyncHandler(BackgroundThread.get().getLooper()); mSyncAdapters.setListener(new RegisteredServicesCacheListener<SyncAdapterType>() { @Override @@ -392,7 +404,7 @@ public class SyncManager { if (!removed) { scheduleSync(null, UserHandle.USER_ALL, SyncOperation.REASON_SERVICE_CHANGED, - type.authority, null, 0 /* no delay */, + type.authority, null, 0 /* no delay */, 0 /* no delay */, false /* onlyThoseWithUnkownSyncableState */); } } @@ -457,6 +469,7 @@ public class SyncManager { mSyncStorageEngine.addStatusChangeListener( ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS, new ISyncStatusObserver.Stub() { + @Override public void onStatusChanged(int which) { // force the sync loop to run if the settings change sendCheckAlarmsMessage(); @@ -566,22 +579,28 @@ public class SyncManager { * @param extras a Map of SyncAdapter-specific information to control * syncs of a specific provider. Can be null. Is ignored * if the url is null. - * @param delay how many milliseconds in the future to wait before performing this - * @param onlyThoseWithUnkownSyncableState + * @param beforeRuntimeMillis milliseconds before runtimeMillis that this sync can run. + * @param runtimeMillis maximum milliseconds in the future to wait before performing sync. + * @param onlyThoseWithUnkownSyncableState Only sync authorities that have unknown state. */ public void scheduleSync(Account requestedAccount, int userId, int reason, - String requestedAuthority, Bundle extras, long delay, - boolean onlyThoseWithUnkownSyncableState) { + String requestedAuthority, Bundle extras, long beforeRuntimeMillis, + long runtimeMillis, boolean onlyThoseWithUnkownSyncableState) { boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); final boolean backgroundDataUsageAllowed = !mBootCompleted || getConnectivityManager().getBackgroundDataSetting(); - if (extras == null) extras = new Bundle(); - + if (extras == null) { + extras = new Bundle(); + } + if (isLoggable) { + Log.d(TAG, "one-time sync for: " + requestedAccount + " " + extras.toString() + " " + + requestedAuthority); + } Boolean expedited = extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false); if (expedited) { - delay = -1; // this means schedule at the front of the queue + runtimeMillis = -1; // this means schedule at the front of the queue } AccountAndUser[] accounts; @@ -686,11 +705,13 @@ public class SyncManager { account.userId, authority); final long backoffTime = backoff != null ? backoff.first : 0; if (isSyncable < 0) { + // Initialisation sync. Bundle newExtras = new Bundle(); newExtras.putBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, true); if (isLoggable) { - Log.v(TAG, "scheduleSync:" - + " delay " + delay + Log.v(TAG, "schedule initialisation Sync:" + + ", delay until " + delayUntil + + ", run by " + 0 + ", source " + source + ", account " + account + ", authority " + authority @@ -698,13 +719,15 @@ public class SyncManager { } scheduleSyncOperation( new SyncOperation(account.account, account.userId, reason, source, - authority, newExtras, 0, backoffTime, delayUntil, - allowParallelSyncs)); + authority, newExtras, 0 /* immediate */, 0 /* No flex time*/, + backoffTime, delayUntil, allowParallelSyncs)); } if (!onlyThoseWithUnkownSyncableState) { if (isLoggable) { Log.v(TAG, "scheduleSync:" - + " delay " + delay + + " delay until " + delayUntil + + " run by " + runtimeMillis + + " flex " + beforeRuntimeMillis + ", source " + source + ", account " + account + ", authority " + authority @@ -712,17 +735,23 @@ public class SyncManager { } scheduleSyncOperation( new SyncOperation(account.account, account.userId, reason, source, - authority, extras, delay, backoffTime, delayUntil, - allowParallelSyncs)); + authority, extras, runtimeMillis, beforeRuntimeMillis, + backoffTime, delayUntil, allowParallelSyncs)); } } } } + /** + * Schedule sync based on local changes to a provider. Occurs within interval + * [LOCAL_SYNC_DELAY, 2*LOCAL_SYNC_DELAY]. + */ public void scheduleLocalSync(Account account, int userId, int reason, String authority) { final Bundle extras = new Bundle(); extras.putBoolean(ContentResolver.SYNC_EXTRAS_UPLOAD, true); - scheduleSync(account, userId, reason, authority, extras, LOCAL_SYNC_DELAY, + scheduleSync(account, userId, reason, authority, extras, + LOCAL_SYNC_DELAY /* earliest run time */, + 2 * LOCAL_SYNC_DELAY /* latest sync time. */, false /* onlyThoseWithUnkownSyncableState */); } @@ -779,6 +808,7 @@ public class SyncManager { } class SyncAlarmIntentReceiver extends BroadcastReceiver { + @Override public void onReceive(Context context, Intent intent) { mHandleAlarmWakeLock.acquire(); sendSyncAlarmMessage(); @@ -947,11 +977,13 @@ public class SyncManager { Log.d(TAG, "retrying sync operation that failed because there was already a " + "sync in progress: " + operation); } - scheduleSyncOperation(new SyncOperation(operation.account, operation.userId, + scheduleSyncOperation( + new SyncOperation( + operation.account, operation.userId, operation.reason, operation.syncSource, operation.authority, operation.extras, - DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000, + DELAY_RETRY_SYNC_IN_PROGRESS_IN_SECONDS * 1000, operation.flexTime, operation.backoff, operation.delayUntil, operation.allowParallelSyncs)); } else if (syncResult.hasSoftError()) { if (isLoggable) { @@ -981,7 +1013,8 @@ public class SyncManager { final Account[] accounts = AccountManagerService.getSingleton().getAccounts(userId); for (Account account : accounts) { scheduleSync(account, userId, SyncOperation.REASON_USER_START, null, null, - 0 /* no delay */, true /* onlyThoseWithUnknownSyncableState */); + 0 /* no delay */, 0 /* No flex */, + true /* onlyThoseWithUnknownSyncableState */); } sendCheckAlarmsMessage(); @@ -1208,7 +1241,10 @@ public class SyncManager { synchronized (mSyncQueue) { sb.setLength(0); mSyncQueue.dump(sb); + // Dump Pending Operations. + getSyncStorageEngine().dumpPendingOperations(sb); } + pw.println(); pw.print(sb.toString()); @@ -1253,10 +1289,11 @@ public class SyncManager { continue; } int row = table.getNumRows(); - SyncStorageEngine.AuthorityInfo settings = - mSyncStorageEngine.getOrCreateAuthority( + Pair<AuthorityInfo, SyncStatusInfo> syncAuthoritySyncStatus = + mSyncStorageEngine.getCopyOfAuthorityWithSyncStatus( account.account, account.userId, syncAdapterType.type.authority); - SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(settings); + SyncStorageEngine.AuthorityInfo settings = syncAuthoritySyncStatus.first; + SyncStatusInfo status = syncAuthoritySyncStatus.second; String authority = settings.authority; if (authority.length() > 50) { @@ -1274,12 +1311,15 @@ public class SyncManager { for (int i = 0; i < settings.periodicSyncs.size(); i++) { - final Pair<Bundle, Long> pair = settings.periodicSyncs.get(0); - final String period = String.valueOf(pair.second); - final String extras = pair.first.size() > 0 ? pair.first.toString() : ""; - final String next = formatTime(status.getPeriodicSyncTime(0) - + pair.second * 1000); - table.set(row + i * 2, 12, period + extras); + final PeriodicSync sync = settings.periodicSyncs.get(i); + final String period = + String.format("[p:%d s, f: %d s]", sync.period, sync.flexTime); + final String extras = + sync.extras.size() > 0 ? + sync.extras.toString() : "Bundle[]"; + final String next = "Next sync: " + formatTime(status.getPeriodicSyncTime(i) + + sync.period * 1000); + table.set(row + i * 2, 12, period + " " + extras); table.set(row + i * 2 + 1, 12, next); } @@ -1746,16 +1786,20 @@ public class SyncManager { public final SyncTimeTracker mSyncTimeTracker = new SyncTimeTracker(); private final HashMap<Pair<Account, String>, PowerManager.WakeLock> mWakeLocks = Maps.newHashMap(); - - private volatile CountDownLatch mReadyToRunLatch = new CountDownLatch(1); + private List<Message> mBootQueue = new ArrayList<Message>(); public void onBootCompleted() { - mBootCompleted = true; - + if (Log.isLoggable(TAG, Log.VERBOSE)) { + Log.v(TAG, "Boot completed, clearing boot queue."); + } doDatabaseCleanup(); - - if (mReadyToRunLatch != null) { - mReadyToRunLatch.countDown(); + synchronized(this) { + // Dispatch any stashed messages. + for (Message message : mBootQueue) { + sendMessage(message); + } + mBootQueue = null; + mBootCompleted = true; } } @@ -1763,7 +1807,8 @@ public class SyncManager { final Pair<Account, String> wakeLockKey = Pair.create(account, authority); PowerManager.WakeLock wakeLock = mWakeLocks.get(wakeLockKey); if (wakeLock == null) { - final String name = SYNC_WAKE_LOCK_PREFIX + "_" + authority + "_" + account; + final String name = SYNC_WAKE_LOCK_PREFIX + "/" + authority + "/" + account.type + + "/" + account.name; wakeLock = mPowerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, name); wakeLock.setReferenceCounted(false); mWakeLocks.put(wakeLockKey, wakeLock); @@ -1771,20 +1816,24 @@ public class SyncManager { return wakeLock; } - private void waitUntilReadyToRun() { - CountDownLatch latch = mReadyToRunLatch; - if (latch != null) { - while (true) { - try { - latch.await(); - mReadyToRunLatch = null; - return; - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - } + /** + * Stash any messages that come to the handler before boot is complete. + * {@link #onBootCompleted()} will disable this and dispatch all the messages collected. + * @param msg Message to dispatch at a later point. + * @return true if a message was enqueued, false otherwise. This is to avoid losing the + * message if we manage to acquire the lock but by the time we do boot has completed. + */ + private boolean tryEnqueueMessageUntilReadyToRun(Message msg) { + synchronized (this) { + if (!mBootCompleted) { + // Need to copy the message bc looper will recycle it. + mBootQueue.add(Message.obtain(msg)); + return true; } + return false; } } + /** * Used to keep track of whether a sync notification is active and who it is for. */ @@ -1812,14 +1861,17 @@ public class SyncManager { super(looper); } + @Override public void handleMessage(Message msg) { + if (tryEnqueueMessageUntilReadyToRun(msg)) { + return; + } + long earliestFuturePollTime = Long.MAX_VALUE; long nextPendingSyncTime = Long.MAX_VALUE; - // Setting the value here instead of a method because we want the dumpsys logs // to have the most recent value used. try { - waitUntilReadyToRun(); mDataConnectionIsConnected = readDataConnectionState(); mSyncManagerWakeLock.acquire(); // Always do this first so that we be sure that any periodic syncs that @@ -1829,7 +1881,7 @@ public class SyncManager { earliestFuturePollTime = scheduleReadyPeriodicSyncs(); switch (msg.what) { case SyncHandler.MESSAGE_CANCEL: { - Pair<Account, String> payload = (Pair<Account, String>)msg.obj; + Pair<Account, String> payload = (Pair<Account, String>) msg.obj; if (Log.isLoggable(TAG, Log.VERBOSE)) { Log.d(TAG, "handleSyncHandlerMessage: MESSAGE_SERVICE_CANCEL: " + payload.first + ", " + payload.second); @@ -1936,6 +1988,10 @@ public class SyncManager { * in milliseconds since boot */ private long scheduleReadyPeriodicSyncs() { + final boolean isLoggable = Log.isLoggable(TAG, Log.VERBOSE); + if (isLoggable) { + Log.v(TAG, "scheduleReadyPeriodicSyncs"); + } final boolean backgroundDataUsageAllowed = getConnectivityManager().getBackgroundDataSetting(); long earliestFuturePollTime = Long.MAX_VALUE; @@ -1947,83 +2003,120 @@ public class SyncManager { final long nowAbsolute = System.currentTimeMillis(); final long shiftedNowAbsolute = (0 < nowAbsolute - mSyncRandomOffsetMillis) - ? (nowAbsolute - mSyncRandomOffsetMillis) : 0; - - ArrayList<SyncStorageEngine.AuthorityInfo> infos = mSyncStorageEngine.getAuthorities(); - for (SyncStorageEngine.AuthorityInfo info : infos) { + ? (nowAbsolute - mSyncRandomOffsetMillis) : 0; + + ArrayList<Pair<AuthorityInfo, SyncStatusInfo>> infos = mSyncStorageEngine + .getCopyOfAllAuthoritiesWithSyncStatus(); + for (Pair<AuthorityInfo, SyncStatusInfo> info : infos) { + final AuthorityInfo authorityInfo = info.first; + final SyncStatusInfo status = info.second; + if (TextUtils.isEmpty(authorityInfo.authority)) { + Log.e(TAG, "Got an empty provider string. Skipping: " + authorityInfo); + continue; + } // skip the sync if the account of this operation no longer exists - if (!containsAccountAndUser(accounts, info.account, info.userId)) { + if (!containsAccountAndUser( + accounts, authorityInfo.account, authorityInfo.userId)) { continue; } - if (!mSyncStorageEngine.getMasterSyncAutomatically(info.userId) - || !mSyncStorageEngine.getSyncAutomatically(info.account, info.userId, - info.authority)) { + if (!mSyncStorageEngine.getMasterSyncAutomatically(authorityInfo.userId) + || !mSyncStorageEngine.getSyncAutomatically( + authorityInfo.account, authorityInfo.userId, + authorityInfo.authority)) { continue; } - if (getIsSyncable(info.account, info.userId, info.authority) + if (getIsSyncable( + authorityInfo.account, authorityInfo.userId, authorityInfo.authority) == 0) { continue; } - SyncStatusInfo status = mSyncStorageEngine.getOrCreateSyncStatus(info); - for (int i = 0, N = info.periodicSyncs.size(); i < N; i++) { - final Bundle extras = info.periodicSyncs.get(i).first; - final Long periodInMillis = info.periodicSyncs.get(i).second * 1000; - // Skip if the period is invalid + for (int i = 0, N = authorityInfo.periodicSyncs.size(); i < N; i++) { + final PeriodicSync sync = authorityInfo.periodicSyncs.get(i); + final Bundle extras = sync.extras; + final long periodInMillis = sync.period * 1000; + final long flexInMillis = sync.flexTime * 1000; + // Skip if the period is invalid. if (periodInMillis <= 0) { continue; } - // find when this periodic sync was last scheduled to run + // Find when this periodic sync was last scheduled to run. final long lastPollTimeAbsolute = status.getPeriodicSyncTime(i); - long remainingMillis - = periodInMillis - (shiftedNowAbsolute % periodInMillis); - + = periodInMillis - (shiftedNowAbsolute % periodInMillis); + long timeSinceLastRunMillis + = (nowAbsolute - lastPollTimeAbsolute); + // Schedule this periodic sync to run early if it's close enough to its next + // runtime, and far enough from its last run time. + // If we are early, there will still be time remaining in this period. + boolean runEarly = remainingMillis <= flexInMillis + && timeSinceLastRunMillis > periodInMillis - flexInMillis; + if (isLoggable) { + Log.v(TAG, "sync: " + i + " for " + authorityInfo.authority + "." + + " period: " + (periodInMillis) + + " flex: " + (flexInMillis) + + " remaining: " + (remainingMillis) + + " time_since_last: " + timeSinceLastRunMillis + + " last poll absol: " + lastPollTimeAbsolute + + " shifted now: " + shiftedNowAbsolute + + " run_early: " + runEarly); + } /* - * Sync scheduling strategy: - * Set the next periodic sync based on a random offset (in seconds). - * - * Also sync right now if any of the following cases hold - * and mark it as having been scheduled - * - * Case 1: This sync is ready to run now. - * Case 2: If the lastPollTimeAbsolute is in the future, - * sync now and reinitialize. This can happen for - * example if the user changed the time, synced and - * changed back. - * Case 3: If we failed to sync at the last scheduled time + * Sync scheduling strategy: Set the next periodic sync + * based on a random offset (in seconds). Also sync right + * now if any of the following cases hold and mark it as + * having been scheduled + * Case 1: This sync is ready to run now. + * Case 2: If the lastPollTimeAbsolute is in the + * future, sync now and reinitialize. This can happen for + * example if the user changed the time, synced and changed + * back. + * Case 3: If we failed to sync at the last scheduled + * time. + * Case 4: This sync is close enough to the time that we can schedule it. */ - if (remainingMillis == periodInMillis // Case 1 + if (runEarly // Case 4 + || remainingMillis == periodInMillis // Case 1 || lastPollTimeAbsolute > nowAbsolute // Case 2 - || (nowAbsolute - lastPollTimeAbsolute - >= periodInMillis)) { // Case 3 + || timeSinceLastRunMillis >= periodInMillis) { // Case 3 // Sync now + final Pair<Long, Long> backoff = mSyncStorageEngine.getBackoff( - info.account, info.userId, info.authority); + authorityInfo.account, authorityInfo.userId, + authorityInfo.authority); final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo; syncAdapterInfo = mSyncAdapters.getServiceInfo( - SyncAdapterType.newKey(info.authority, info.account.type), - info.userId); + SyncAdapterType.newKey( + authorityInfo.authority, authorityInfo.account.type), + authorityInfo.userId); if (syncAdapterInfo == null) { continue; } + mSyncStorageEngine.setPeriodicSyncTime(authorityInfo.ident, + authorityInfo.periodicSyncs.get(i), nowAbsolute); scheduleSyncOperation( - new SyncOperation(info.account, info.userId, + new SyncOperation(authorityInfo.account, authorityInfo.userId, SyncOperation.REASON_PERIODIC, SyncStorageEngine.SOURCE_PERIODIC, - info.authority, extras, 0 /* delay */, - backoff != null ? backoff.first : 0, + authorityInfo.authority, extras, + 0 /* runtime */, 0 /* flex */, + backoff != null ? backoff.first : 0, mSyncStorageEngine.getDelayUntilTime( - info.account, info.userId, info.authority), + authorityInfo.account, authorityInfo.userId, + authorityInfo.authority), syncAdapterInfo.type.allowParallelSyncs())); - status.setPeriodicSyncTime(i, nowAbsolute); + + } + // Compute when this periodic sync should next run. + long nextPollTimeAbsolute; + if (runEarly) { + // Add the time remaining so we don't get out of phase. + nextPollTimeAbsolute = nowAbsolute + periodInMillis + remainingMillis; + } else { + nextPollTimeAbsolute = nowAbsolute + remainingMillis; } - // Compute when this periodic sync should next run - final long nextPollTimeAbsolute = nowAbsolute + remainingMillis; - - // remember this time if it is earlier than earliestFuturePollTime if (nextPollTimeAbsolute < earliestFuturePollTime) { earliestFuturePollTime = nextPollTimeAbsolute; } @@ -2035,10 +2128,9 @@ public class SyncManager { } // convert absolute time to elapsed time - return SystemClock.elapsedRealtime() - + ((earliestFuturePollTime < nowAbsolute) - ? 0 - : (earliestFuturePollTime - nowAbsolute)); + return SystemClock.elapsedRealtime() + + ((earliestFuturePollTime < nowAbsolute) ? + 0 : (earliestFuturePollTime - nowAbsolute)); } private long maybeStartNextSyncLocked() { @@ -2088,8 +2180,8 @@ public class SyncManager { Log.v(TAG, "build the operation array, syncQueue size is " + mSyncQueue.getOperations().size()); } - final Iterator<SyncOperation> operationIterator = mSyncQueue.getOperations() - .iterator(); + final Iterator<SyncOperation> operationIterator = + mSyncQueue.getOperations().iterator(); final ActivityManager activityManager = (ActivityManager) mContext.getSystemService(Context.ACTIVITY_SERVICE); @@ -2097,40 +2189,61 @@ public class SyncManager { while (operationIterator.hasNext()) { final SyncOperation op = operationIterator.next(); - // drop the sync if the account of this operation no longer exists + // Drop the sync if the account of this operation no longer exists. if (!containsAccountAndUser(accounts, op.account, op.userId)) { operationIterator.remove(); mSyncStorageEngine.deleteFromPending(op.pendingOperation); + if (isLoggable) { + Log.v(TAG, " Dropping sync operation: account doesn't exist."); + } continue; } - // drop this sync request if it isn't syncable + // Drop this sync request if it isn't syncable. int syncableState = getIsSyncable( op.account, op.userId, op.authority); if (syncableState == 0) { operationIterator.remove(); mSyncStorageEngine.deleteFromPending(op.pendingOperation); + if (isLoggable) { + Log.v(TAG, " Dropping sync operation: isSyncable == 0."); + } continue; } - // if the user in not running, drop the request + // If the user is not running, drop the request. if (!activityManager.isUserRunning(op.userId)) { final UserInfo userInfo = mUserManager.getUserInfo(op.userId); if (userInfo == null) { removedUsers.add(op.userId); } + if (isLoggable) { + Log.v(TAG, " Dropping sync operation: user not running."); + } continue; } - // if the next run time is in the future, meaning there are no syncs ready - // to run, return the time - if (op.effectiveRunTime > now) { + // If the next run time is in the future, even given the flexible scheduling, + // return the time. + if (op.effectiveRunTime - op.flexTime > now) { if (nextReadyToRunTime > op.effectiveRunTime) { nextReadyToRunTime = op.effectiveRunTime; } + if (isLoggable) { + Log.v(TAG, " Dropping sync operation: Sync too far in future."); + } + continue; + } + + // If the op isn't allowed on metered networks and we're on one, drop it. + if (getConnectivityManager().isActiveNetworkMetered() + && op.isMeteredDisallowed()) { + operationIterator.remove(); + mSyncStorageEngine.deleteFromPending(op.pendingOperation); continue; } + // TODO: change this behaviour for non-registered syncs. final RegisteredServicesCache.ServiceInfo<SyncAdapterType> syncAdapterInfo; syncAdapterInfo = mSyncAdapters.getServiceInfo( SyncAdapterType.newKey(op.authority, op.account.type), op.userId); @@ -2171,7 +2284,7 @@ public class SyncManager { } // find the next operation to dispatch, if one is ready - // iterate from the top, keep issuing (while potentially cancelling existing syncs) + // iterate from the top, keep issuing (while potentially canceling existing syncs) // until the quotas are filled. // once the quotas are filled iterate once more to find when the next one would be // (also considering pre-emption reasons). @@ -2451,11 +2564,13 @@ public class SyncManager { } if (syncResult != null && syncResult.fullSyncRequested) { - scheduleSyncOperation(new SyncOperation(syncOperation.account, syncOperation.userId, - syncOperation.reason, - syncOperation.syncSource, syncOperation.authority, new Bundle(), 0, - syncOperation.backoff, syncOperation.delayUntil, - syncOperation.allowParallelSyncs)); + scheduleSyncOperation( + new SyncOperation(syncOperation.account, syncOperation.userId, + syncOperation.reason, + syncOperation.syncSource, syncOperation.authority, new Bundle(), + 0 /* delay */, 0 /* flex */, + syncOperation.backoff, syncOperation.delayUntil, + syncOperation.allowParallelSyncs)); } // no need to schedule an alarm, as that will be done by our caller. } @@ -2625,9 +2740,12 @@ public class SyncManager { // determine if we need to set or cancel the alarm boolean shouldSet = false; boolean shouldCancel = false; - final boolean alarmIsActive = mAlarmScheduleTime != null; + final boolean alarmIsActive = (mAlarmScheduleTime != null) && (now < mAlarmScheduleTime); final boolean needAlarm = alarmTime != Long.MAX_VALUE; if (needAlarm) { + // Need the alarm if + // - it's currently not set + // - if the alarm is set in the past. if (!alarmIsActive || alarmTime < mAlarmScheduleTime) { shouldSet = true; } @@ -2635,7 +2753,7 @@ public class SyncManager { shouldCancel = alarmIsActive; } - // set or cancel the alarm as directed + // Set or cancel the alarm as directed. ensureAlarmService(); if (shouldSet) { if (Log.isLoggable(TAG, Log.VERBOSE)) { @@ -2644,7 +2762,7 @@ public class SyncManager { + " secs from now"); } mAlarmScheduleTime = alarmTime; - mAlarmService.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, + mAlarmService.setExact(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mSyncAlarmIntent); } else if (shouldCancel) { mAlarmScheduleTime = null; diff --git a/services/java/com/android/server/content/SyncOperation.java b/services/java/com/android/server/content/SyncOperation.java index eaad98229206..485674782aeb 100644 --- a/services/java/com/android/server/content/SyncOperation.java +++ b/services/java/com/android/server/content/SyncOperation.java @@ -18,13 +18,18 @@ package com.android.server.content; import android.accounts.Account; import android.content.pm.PackageManager; +import android.content.ComponentName; import android.content.ContentResolver; +import android.content.SyncRequest; import android.os.Bundle; import android.os.SystemClock; +import android.util.Pair; /** * Value type that represents a sync operation. - * @hide + * TODO: This is the class to flesh out with all the scheduling data - metered/unmetered, + * transfer-size, etc. + * {@hide} */ public class SyncOperation implements Comparable { public static final int REASON_BACKGROUND_DATA_SETTINGS_CHANGED = -1; @@ -32,7 +37,9 @@ public class SyncOperation implements Comparable { public static final int REASON_SERVICE_CHANGED = -3; public static final int REASON_PERIODIC = -4; public static final int REASON_IS_SYNCABLE = -5; + /** Sync started because it has just been set to sync automatically. */ public static final int REASON_SYNC_AUTO = -6; + /** Sync started because master sync automatically has been set to true. */ public static final int REASON_MASTER_SYNC_AUTO = -7; public static final int REASON_USER_START = -8; @@ -47,75 +54,109 @@ public class SyncOperation implements Comparable { "UserStart", }; + /** Account info to identify a SyncAdapter registered with the system. */ public final Account account; + /** Authority info to identify a SyncAdapter registered with the system. */ + public final String authority; + /** Service to which this operation will bind to perform the sync. */ + public final ComponentName service; public final int userId; public final int reason; public int syncSource; - public String authority; public final boolean allowParallelSyncs; public Bundle extras; public final String key; - public long earliestRunTime; public boolean expedited; public SyncStorageEngine.PendingOperation pendingOperation; + /** Elapsed real time in millis at which to run this sync. */ + public long latestRunTime; + /** Set by the SyncManager in order to delay retries. */ public Long backoff; + /** Specified by the adapter to delay subsequent sync operations. */ public long delayUntil; + /** + * Elapsed real time in millis when this sync will be run. + * Depends on max(backoff, latestRunTime, and delayUntil). + */ public long effectiveRunTime; + /** Amount of time before {@link effectiveRunTime} from which this sync can run. */ + public long flexTime; public SyncOperation(Account account, int userId, int reason, int source, String authority, - Bundle extras, long delayInMs, long backoff, long delayUntil, - boolean allowParallelSyncs) { + Bundle extras, long runTimeFromNow, long flexTime, long backoff, + long delayUntil, boolean allowParallelSyncs) { + this.service = null; this.account = account; + this.authority = authority; this.userId = userId; this.reason = reason; this.syncSource = source; - this.authority = authority; this.allowParallelSyncs = allowParallelSyncs; this.extras = new Bundle(extras); - removeFalseExtra(ContentResolver.SYNC_EXTRAS_UPLOAD); - removeFalseExtra(ContentResolver.SYNC_EXTRAS_MANUAL); - removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS); - removeFalseExtra(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF); - removeFalseExtra(ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY); - removeFalseExtra(ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS); - removeFalseExtra(ContentResolver.SYNC_EXTRAS_EXPEDITED); - removeFalseExtra(ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS); + cleanBundle(this.extras); this.delayUntil = delayUntil; this.backoff = backoff; final long now = SystemClock.elapsedRealtime(); - if (delayInMs < 0) { + // Checks the extras bundle. Must occur after we set the internal bundle. + if (runTimeFromNow < 0 || isExpedited()) { this.expedited = true; - this.earliestRunTime = now; + this.latestRunTime = now; + this.flexTime = 0; } else { this.expedited = false; - this.earliestRunTime = now + delayInMs; + this.latestRunTime = now + runTimeFromNow; + this.flexTime = flexTime; } updateEffectiveRunTime(); this.key = toKey(); } - private void removeFalseExtra(String extraName) { - if (!extras.getBoolean(extraName, false)) { - extras.remove(extraName); + /** + * Make sure the bundle attached to this SyncOperation doesn't have unnecessary + * flags set. + * @param bundle to clean. + */ + private void cleanBundle(Bundle bundle) { + removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_UPLOAD); + removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_MANUAL); + removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_SETTINGS); + removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF); + removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DO_NOT_RETRY); + removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISCARD_LOCAL_DELETIONS); + removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_EXPEDITED); + removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_OVERRIDE_TOO_MANY_DELETIONS); + removeFalseExtra(bundle, ContentResolver.SYNC_EXTRAS_DISALLOW_METERED); + + // Remove Config data. + bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_UPLOAD); + bundle.remove(ContentResolver.SYNC_EXTRAS_EXPECTED_DOWNLOAD); + } + + private void removeFalseExtra(Bundle bundle, String extraName) { + if (!bundle.getBoolean(extraName, false)) { + bundle.remove(extraName); } } + /** Only used to immediately reschedule a sync. */ SyncOperation(SyncOperation other) { + this.service = other.service; this.account = other.account; + this.authority = other.authority; this.userId = other.userId; this.reason = other.reason; this.syncSource = other.syncSource; - this.authority = other.authority; this.extras = new Bundle(other.extras); this.expedited = other.expedited; - this.earliestRunTime = SystemClock.elapsedRealtime(); + this.latestRunTime = SystemClock.elapsedRealtime(); + this.flexTime = 0L; this.backoff = other.backoff; - this.delayUntil = other.delayUntil; this.allowParallelSyncs = other.allowParallelSyncs; this.updateEffectiveRunTime(); this.key = toKey(); } + @Override public String toString() { return dump(null, true); } @@ -131,8 +172,8 @@ public class SyncOperation implements Comparable { .append(authority) .append(", ") .append(SyncStorageEngine.SOURCES[syncSource]) - .append(", earliestRunTime ") - .append(earliestRunTime); + .append(", latestRunTime ") + .append(latestRunTime); if (expedited) { sb.append(", EXPEDITED"); } @@ -170,23 +211,38 @@ public class SyncOperation implements Comparable { } } + public boolean isMeteredDisallowed() { + return extras.getBoolean(ContentResolver.SYNC_EXTRAS_DISALLOW_METERED, false); + } + public boolean isInitialization() { return extras.getBoolean(ContentResolver.SYNC_EXTRAS_INITIALIZE, false); } public boolean isExpedited() { - return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false); + return extras.getBoolean(ContentResolver.SYNC_EXTRAS_EXPEDITED, false) || expedited; } public boolean ignoreBackoff() { return extras.getBoolean(ContentResolver.SYNC_EXTRAS_IGNORE_BACKOFF, false); } + /** Changed in V3. */ private String toKey() { StringBuilder sb = new StringBuilder(); - sb.append("authority: ").append(authority); - sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type - + "}"); + if (service == null) { + sb.append("authority: ").append(authority); + sb.append(" account {name=" + account.name + ", user=" + userId + ", type=" + account.type + + "}"); + } else { + sb.append("service {package=" ) + .append(service.getPackageName()) + .append(" user=") + .append(userId) + .append(", class=") + .append(service.getClassName()) + .append("}"); + } sb.append(" extras: "); extrasToStringBuilder(extras, sb); return sb.toString(); @@ -200,25 +256,39 @@ public class SyncOperation implements Comparable { sb.append("]"); } + /** + * Update the effective run time of this Operation based on latestRunTime (specified at + * creation time of sync), delayUntil (specified by SyncAdapter), or backoff (specified by + * SyncManager on soft failures). + */ public void updateEffectiveRunTime() { - effectiveRunTime = ignoreBackoff() - ? earliestRunTime - : Math.max( - Math.max(earliestRunTime, delayUntil), - backoff); + // Regardless of whether we're in backoff or honouring a delayUntil, we still incorporate + // the flex time provided by the developer. + effectiveRunTime = ignoreBackoff() ? + latestRunTime : + Math.max(Math.max(latestRunTime, delayUntil), backoff); } + /** + * SyncOperations are sorted based on their earliest effective run time. + * This comparator is used to sort the SyncOps at a given time when + * deciding which to run, so earliest run time is the best criteria. + */ + @Override public int compareTo(Object o) { - SyncOperation other = (SyncOperation)o; - + SyncOperation other = (SyncOperation) o; if (expedited != other.expedited) { return expedited ? -1 : 1; } - - if (effectiveRunTime == other.effectiveRunTime) { + long thisIntervalStart = Math.max(effectiveRunTime - flexTime, 0); + long otherIntervalStart = Math.max( + other.effectiveRunTime - other.flexTime, 0); + if (thisIntervalStart < otherIntervalStart) { + return -1; + } else if (otherIntervalStart < thisIntervalStart) { + return 1; + } else { return 0; } - - return effectiveRunTime < other.effectiveRunTime ? -1 : 1; } } diff --git a/services/java/com/android/server/content/SyncQueue.java b/services/java/com/android/server/content/SyncQueue.java index 951e92cf32cd..6f3fe6e1d37b 100644 --- a/services/java/com/android/server/content/SyncQueue.java +++ b/services/java/com/android/server/content/SyncQueue.java @@ -73,7 +73,7 @@ public class SyncQueue { } SyncOperation syncOperation = new SyncOperation( op.account, op.userId, op.reason, op.syncSource, op.authority, op.extras, - 0 /* delay */, backoff != null ? backoff.first : 0, + 0 /* delay */, 0 /* flex */, backoff != null ? backoff.first : 0, mSyncStorageEngine.getDelayUntilTime(op.account, op.userId, op.authority), syncAdapterInfo.type.allowParallelSyncs()); syncOperation.expedited = op.expedited; @@ -86,35 +86,40 @@ public class SyncQueue { return add(operation, null /* this is not coming from the database */); } + /** + * Adds a SyncOperation to the queue and creates a PendingOperation object to track that sync. + * If an operation is added that already exists, the existing operation is updated if the newly + * added operation occurs before (or the interval overlaps). + */ private boolean add(SyncOperation operation, SyncStorageEngine.PendingOperation pop) { - // - if an operation with the same key exists and this one should run earlier, - // update the earliestRunTime of the existing to the new time - // - if an operation with the same key exists and if this one should run - // later, ignore it - // - if no operation exists then add the new one + // If an operation with the same key exists and this one should run sooner/overlaps, + // replace the run interval of the existing operation with this new one. + // Complications: what if the existing operation is expedited but the new operation has an + // earlier run time? Will not be a problem for periodic syncs (no expedited flag), and for + // one-off syncs we only change it if the new sync is sooner. final String operationKey = operation.key; final SyncOperation existingOperation = mOperationsMap.get(operationKey); if (existingOperation != null) { boolean changed = false; - if (existingOperation.expedited == operation.expedited) { - final long newRunTime = - Math.min(existingOperation.earliestRunTime, operation.earliestRunTime); - if (existingOperation.earliestRunTime != newRunTime) { - existingOperation.earliestRunTime = newRunTime; - changed = true; - } - } else { - if (operation.expedited) { - existingOperation.expedited = true; - changed = true; - } + if (operation.compareTo(existingOperation) <= 0 ) { + existingOperation.expedited = operation.expedited; + long newRunTime = + Math.min(existingOperation.latestRunTime, operation.latestRunTime); + // Take smaller runtime. + existingOperation.latestRunTime = newRunTime; + // Take newer flextime. + existingOperation.flexTime = operation.flexTime; + changed = true; } return changed; } operation.pendingOperation = pop; + // Don't update the PendingOp if one already exists. This really is just a placeholder, + // no actual scheduling info is placed here. + // TODO: Change this to support service components. if (operation.pendingOperation == null) { pop = new SyncStorageEngine.PendingOperation( operation.account, operation.userId, operation.reason, operation.syncSource, diff --git a/services/java/com/android/server/content/SyncStorageEngine.java b/services/java/com/android/server/content/SyncStorageEngine.java index 5b8d26ff77cd..41ef22959be5 100644 --- a/services/java/com/android/server/content/SyncStorageEngine.java +++ b/services/java/com/android/server/content/SyncStorageEngine.java @@ -18,6 +18,7 @@ package com.android.server.content; import android.accounts.Account; import android.accounts.AccountAndUser; +import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.ISyncStatusObserver; @@ -52,6 +53,7 @@ import org.xmlpull.v1.XmlSerializer; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; +import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.HashMap; @@ -70,7 +72,7 @@ public class SyncStorageEngine extends Handler { private static final String TAG = "SyncManager"; private static final boolean DEBUG = false; - private static final boolean DEBUG_FILE = false; + private static final String TAG_FILE = "SyncManagerFile"; private static final String XML_ATTR_NEXT_AUTHORITY_ID = "nextAuthorityId"; private static final String XML_ATTR_LISTEN_FOR_TICKLES = "listen-for-tickles"; @@ -79,8 +81,15 @@ public class SyncStorageEngine extends Handler { private static final String XML_ATTR_USER = "user"; private static final String XML_TAG_LISTEN_FOR_TICKLES = "listenForTickles"; + /** Default time for a periodic sync. */ private static final long DEFAULT_POLL_FREQUENCY_SECONDS = 60 * 60 * 24; // One day + /** Percentage of period that is flex by default, if no flex is set. */ + private static final double DEFAULT_FLEX_PERCENT_SYNC = 0.04; + + /** Lower bound on sync time from which we assign a default flex time. */ + private static final long DEFAULT_MIN_FLEX_ALLOWED_SECS = 5; + @VisibleForTesting static final long MILLIS_IN_4WEEKS = 1000L * 60 * 60 * 24 * 7 * 4; @@ -153,12 +162,13 @@ public class SyncStorageEngine extends Handler { final int syncSource; final String authority; final Bundle extras; // note: read-only. + final ComponentName serviceName; final boolean expedited; int authorityId; byte[] flatExtras; - PendingOperation(Account account, int userId, int reason,int source, + PendingOperation(Account account, int userId, int reason, int source, String authority, Bundle extras, boolean expedited) { this.account = account; this.userId = userId; @@ -168,6 +178,7 @@ public class SyncStorageEngine extends Handler { this.extras = extras != null ? new Bundle(extras) : extras; this.expedited = expedited; this.authorityId = -1; + this.serviceName = null; } PendingOperation(PendingOperation other) { @@ -179,6 +190,7 @@ public class SyncStorageEngine extends Handler { this.extras = other.extras; this.authorityId = other.authorityId; this.expedited = other.expedited; + this.serviceName = other.serviceName; } } @@ -193,6 +205,7 @@ public class SyncStorageEngine extends Handler { } public static class AuthorityInfo { + final ComponentName service; final Account account; final int userId; final String authority; @@ -202,7 +215,7 @@ public class SyncStorageEngine extends Handler { long backoffTime; long backoffDelay; long delayUntil; - final ArrayList<Pair<Bundle, Long>> periodicSyncs; + final ArrayList<PeriodicSync> periodicSyncs; /** * Copy constructor for making deep-ish copies. Only the bundles stored @@ -214,30 +227,70 @@ public class SyncStorageEngine extends Handler { account = toCopy.account; userId = toCopy.userId; authority = toCopy.authority; + service = toCopy.service; ident = toCopy.ident; enabled = toCopy.enabled; syncable = toCopy.syncable; backoffTime = toCopy.backoffTime; backoffDelay = toCopy.backoffDelay; delayUntil = toCopy.delayUntil; - periodicSyncs = new ArrayList<Pair<Bundle, Long>>(); - for (Pair<Bundle, Long> sync : toCopy.periodicSyncs) { + periodicSyncs = new ArrayList<PeriodicSync>(); + for (PeriodicSync sync : toCopy.periodicSyncs) { // Still not a perfect copy, because we are just copying the mappings. - periodicSyncs.add(Pair.create(new Bundle(sync.first), sync.second)); + periodicSyncs.add(new PeriodicSync(sync)); } } + /** + * Create an authority with one periodic sync scheduled with an empty bundle and syncing + * every day. An empty bundle is considered equal to any other bundle see + * {@link PeriodicSync.syncExtrasEquals}. + * @param account Account that this authority syncs. + * @param userId which user this sync is registered for. + * @param userId user for which this authority is registered. + * @param ident id of this authority. + */ AuthorityInfo(Account account, int userId, String authority, int ident) { this.account = account; this.userId = userId; this.authority = authority; + this.service = null; this.ident = ident; enabled = SYNC_ENABLED_DEFAULT; syncable = -1; // default to "unknown" backoffTime = -1; // if < 0 then we aren't in backoff mode backoffDelay = -1; // if < 0 then we aren't in backoff mode - periodicSyncs = new ArrayList<Pair<Bundle, Long>>(); - periodicSyncs.add(Pair.create(new Bundle(), DEFAULT_POLL_FREQUENCY_SECONDS)); + periodicSyncs = new ArrayList<PeriodicSync>(); + // Old version adds one periodic sync a day. + periodicSyncs.add(new PeriodicSync(account, authority, + new Bundle(), + DEFAULT_POLL_FREQUENCY_SECONDS, + calculateDefaultFlexTime(DEFAULT_POLL_FREQUENCY_SECONDS))); + } + + /** + * Create an authority with one periodic sync scheduled with an empty bundle and syncing + * every day using a sync service. + * @param cname sync service identifier. + * @param userId user for which this authority is registered. + * @param ident id of this authority. + */ + AuthorityInfo(ComponentName cname, int userId, int ident) { + this.account = null; + this.userId = userId; + this.authority = null; + this.service = cname; + this.ident = ident; + // Sync service is always enabled. + enabled = true; + syncable = -1; // default to "unknown" + backoffTime = -1; // if < 0 then we aren't in backoff mode + backoffDelay = -1; // if < 0 then we aren't in backoff mode + periodicSyncs = new ArrayList<PeriodicSync>(); + periodicSyncs.add(new PeriodicSync(account, authority, + new Bundle(), + DEFAULT_POLL_FREQUENCY_SECONDS, + calculateDefaultFlexTime(DEFAULT_POLL_FREQUENCY_SECONDS))); } } @@ -303,6 +356,10 @@ public class SyncStorageEngine extends Handler { private final RemoteCallbackList<ISyncStatusObserver> mChangeListeners = new RemoteCallbackList<ISyncStatusObserver>(); + /** Reverse mapping for component name -> <userid -> authority id>. */ + private final HashMap<ComponentName, SparseArray<AuthorityInfo>> mServices = + new HashMap<ComponentName, SparseArray<AuthorityInfo>>(); + private int mNextAuthorityId = 0; // We keep 4 weeks of stats. @@ -364,9 +421,12 @@ public class SyncStorageEngine extends Handler { File systemDir = new File(dataDir, "system"); File syncDir = new File(systemDir, "sync"); syncDir.mkdirs(); + + maybeDeleteLegacyPendingInfoLocked(syncDir); + mAccountInfoFile = new AtomicFile(new File(syncDir, "accounts.xml")); mStatusFile = new AtomicFile(new File(syncDir, "status.bin")); - mPendingFile = new AtomicFile(new File(syncDir, "pending.bin")); + mPendingFile = new AtomicFile(new File(syncDir, "pending.xml")); mStatisticsFile = new AtomicFile(new File(syncDir, "stats.bin")); readAccountInfoLocked(); @@ -435,6 +495,28 @@ public class SyncStorageEngine extends Handler { } } + /** + * Figure out a reasonable flex time for cases where none is provided (old api calls). + * @param syncTimeSeconds requested sync time from now. + * @return amount of seconds before syncTimeSeconds that the sync can occur. + * I.e. + * earliest_sync_time = syncTimeSeconds - calculateDefaultFlexTime(syncTimeSeconds) + * The flex time is capped at a percentage of the {@link DEFAULT_POLL_FREQUENCY_SECONDS}. + */ + public static long calculateDefaultFlexTime(long syncTimeSeconds) { + if (syncTimeSeconds < DEFAULT_MIN_FLEX_ALLOWED_SECS) { + // Small enough sync request time that we don't add flex time - developer probably + // wants to wait for an operation to occur before syncing so we honour the + // request time. + return 0L; + } else if (syncTimeSeconds < DEFAULT_POLL_FREQUENCY_SECONDS) { + return (long) (syncTimeSeconds * DEFAULT_FLEX_PERCENT_SYNC); + } else { + // Large enough sync request time that we cap the flex time. + return (long) (DEFAULT_POLL_FREQUENCY_SECONDS * DEFAULT_FLEX_PERCENT_SYNC); + } + } + private void reportChange(int which) { ArrayList<ISyncStatusObserver> reports = null; synchronized (mAuthorities) { @@ -552,8 +634,8 @@ public class SyncStorageEngine extends Handler { + ", user " + userId + " -> " + syncable); } synchronized (mAuthorities) { - AuthorityInfo authority = getOrCreateAuthorityLocked(account, userId, providerName, -1, - false); + AuthorityInfo authority = + getOrCreateAuthorityLocked(account, userId, providerName, -1, false); if (authority.syncable == syncable) { if (DEBUG) { Log.d(TAG, "setIsSyncable: already set to " + syncable + ", doing nothing"); @@ -598,7 +680,8 @@ public class SyncStorageEngine extends Handler { continue; } for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) { - if (providerName != null && !providerName.equals(authorityInfo.authority)) { + if (providerName != null + && !providerName.equals(authorityInfo.authority)) { continue; } if (authorityInfo.backoffTime != nextSyncTime @@ -627,28 +710,31 @@ public class SyncStorageEngine extends Handler { } } - public void clearAllBackoffs(SyncQueue syncQueue) { + /** + * Callers of this function need to hold a lock for syncQueue object passed in. Bear in mind + * this function grabs the lock for {@link #mAuthorities} + * @param syncQueue queue containing pending sync operations. + */ + public void clearAllBackoffsLocked(SyncQueue syncQueue) { boolean changed = false; synchronized (mAuthorities) { - synchronized (syncQueue) { - for (AccountInfo accountInfo : mAccounts.values()) { - for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) { - if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE - || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) { - if (DEBUG) { - Log.v(TAG, "clearAllBackoffs:" - + " authority:" + authorityInfo.authority - + " account:" + accountInfo.accountAndUser.account.name - + " user:" + accountInfo.accountAndUser.userId - + " backoffTime was: " + authorityInfo.backoffTime - + " backoffDelay was: " + authorityInfo.backoffDelay); - } - authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE; - authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE; - syncQueue.onBackoffChanged(accountInfo.accountAndUser.account, - accountInfo.accountAndUser.userId, authorityInfo.authority, 0); - changed = true; + for (AccountInfo accountInfo : mAccounts.values()) { + for (AuthorityInfo authorityInfo : accountInfo.authorities.values()) { + if (authorityInfo.backoffTime != NOT_IN_BACKOFF_MODE + || authorityInfo.backoffDelay != NOT_IN_BACKOFF_MODE) { + if (DEBUG) { + Log.v(TAG, "clearAllBackoffs:" + + " authority:" + authorityInfo.authority + + " account:" + accountInfo.accountAndUser.account.name + + " user:" + accountInfo.accountAndUser.userId + + " backoffTime was: " + authorityInfo.backoffTime + + " backoffDelay was: " + authorityInfo.backoffDelay); } + authorityInfo.backoffTime = NOT_IN_BACKOFF_MODE; + authorityInfo.backoffDelay = NOT_IN_BACKOFF_MODE; + syncQueue.onBackoffChanged(accountInfo.accountAndUser.account, + accountInfo.accountAndUser.userId, authorityInfo.authority, 0); + changed = true; } } } @@ -688,62 +774,68 @@ public class SyncStorageEngine extends Handler { } } - private void updateOrRemovePeriodicSync(Account account, int userId, String providerName, - Bundle extras, - long period, boolean add) { - if (period <= 0) { - period = 0; - } - if (extras == null) { - extras = new Bundle(); - } + private void updateOrRemovePeriodicSync(PeriodicSync toUpdate, int userId, boolean add) { if (DEBUG) { - Log.v(TAG, "addOrRemovePeriodicSync: " + account + ", user " + userId - + ", provider " + providerName - + " -> period " + period + ", extras " + extras); + Log.v(TAG, "addOrRemovePeriodicSync: " + toUpdate.account + ", user " + userId + + ", provider " + toUpdate.authority + + " -> period " + toUpdate.period + ", extras " + toUpdate.extras); } synchronized (mAuthorities) { + if (toUpdate.period <= 0 && add) { + Log.e(TAG, "period < 0, should never happen in updateOrRemovePeriodicSync: add-" + + add); + } + if (toUpdate.extras == null) { + Log.e(TAG, "null extras, should never happen in updateOrRemovePeriodicSync: add-" + + add); + } try { AuthorityInfo authority = - getOrCreateAuthorityLocked(account, userId, providerName, -1, false); + getOrCreateAuthorityLocked(toUpdate.account, userId, toUpdate.authority, + -1, false); if (add) { - // add this periodic sync if one with the same extras doesn't already - // exist in the periodicSyncs array + // add this periodic sync if an equivalent periodic doesn't already exist. boolean alreadyPresent = false; for (int i = 0, N = authority.periodicSyncs.size(); i < N; i++) { - Pair<Bundle, Long> syncInfo = authority.periodicSyncs.get(i); - final Bundle existingExtras = syncInfo.first; - if (PeriodicSync.syncExtrasEquals(existingExtras, extras)) { - if (syncInfo.second == period) { + PeriodicSync syncInfo = authority.periodicSyncs.get(i); + if (PeriodicSync.syncExtrasEquals( + toUpdate.extras, + syncInfo.extras)) { + if (toUpdate.period == syncInfo.period && + toUpdate.flexTime == syncInfo.flexTime) { + // Absolutely the same. return; } - authority.periodicSyncs.set(i, Pair.create(extras, period)); + authority.periodicSyncs.set(i, new PeriodicSync(toUpdate)); alreadyPresent = true; break; } } - // if we added an entry to the periodicSyncs array also add an entry to - // the periodic syncs status to correspond to it + // If we added an entry to the periodicSyncs array also add an entry to + // the periodic syncs status to correspond to it. if (!alreadyPresent) { - authority.periodicSyncs.add(Pair.create(extras, period)); + authority.periodicSyncs.add(new PeriodicSync(toUpdate)); SyncStatusInfo status = getOrCreateSyncStatusLocked(authority.ident); - status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0); + status.setPeriodicSyncTime(authority.periodicSyncs.size() - 1, 0L); } } else { - // remove any periodic syncs that match the authority and extras + // Remove any periodic syncs that match the authority and extras. SyncStatusInfo status = mSyncStatus.get(authority.ident); boolean changed = false; - Iterator<Pair<Bundle, Long>> iterator = authority.periodicSyncs.iterator(); + Iterator<PeriodicSync> iterator = authority.periodicSyncs.iterator(); int i = 0; while (iterator.hasNext()) { - Pair<Bundle, Long> syncInfo = iterator.next(); - if (PeriodicSync.syncExtrasEquals(syncInfo.first, extras)) { + PeriodicSync syncInfo = iterator.next(); + if (PeriodicSync.syncExtrasEquals(syncInfo.extras, toUpdate.extras)) { iterator.remove(); changed = true; - // if we removed an entry from the periodicSyncs array also + // If we removed an entry from the periodicSyncs array also // remove the corresponding entry from the status if (status != null) { status.removePeriodicSyncTime(i); + } else { + Log.e(TAG, "Tried removing sync status on remove periodic sync but" + + "did not find it."); } } else { i++; @@ -762,16 +854,12 @@ public class SyncStorageEngine extends Handler { reportChange(ContentResolver.SYNC_OBSERVER_TYPE_SETTINGS); } - public void addPeriodicSync(Account account, int userId, String providerName, Bundle extras, - long pollFrequency) { - updateOrRemovePeriodicSync(account, userId, providerName, extras, pollFrequency, - true /* add */); + public void addPeriodicSync(PeriodicSync toAdd, int userId) { + updateOrRemovePeriodicSync(toAdd, userId, true /* add */); } - public void removePeriodicSync(Account account, int userId, String providerName, - Bundle extras) { - updateOrRemovePeriodicSync(account, userId, providerName, extras, 0 /* period, ignored */, - false /* remove */); + public void removePeriodicSync(PeriodicSync toRemove, int userId) { + updateOrRemovePeriodicSync(toRemove, userId, false /* remove */); } public List<PeriodicSync> getPeriodicSyncs(Account account, int userId, String providerName) { @@ -780,9 +868,9 @@ public class SyncStorageEngine extends Handler { AuthorityInfo authority = getAuthorityLocked(account, userId, providerName, "getPeriodicSyncs"); if (authority != null) { - for (Pair<Bundle, Long> item : authority.periodicSyncs) { - syncs.add(new PeriodicSync(account, providerName, item.first, - item.second)); + for (PeriodicSync item : authority.periodicSyncs) { + // Copy and send out. Necessary for thread-safety although it's parceled. + syncs.add(new PeriodicSync(item)); } } } @@ -813,14 +901,6 @@ public class SyncStorageEngine extends Handler { } } - public AuthorityInfo getOrCreateAuthority(Account account, int userId, String authority) { - synchronized (mAuthorities) { - return getOrCreateAuthorityLocked(account, userId, authority, - -1 /* assign a new identifier if creating a new authority */, - true /* write to storage if this results in a change */); - } - } - public void removeAuthority(Account account, int userId, String authority) { synchronized (mAuthorities) { removeAuthorityLocked(account, userId, authority, true /* doWrite */); @@ -883,6 +963,14 @@ public class SyncStorageEngine extends Handler { return op; } + /** + * Remove from list of pending operations. If successful, search through list for matching + * authorities. If there are no more pending syncs for the same authority/account/userid, + * update the SyncStatusInfo for that authority(authority here is the internal representation + * of a 'sync operation'. + * @param op + * @return + */ public boolean deleteFromPending(PendingOperation op) { boolean res = false; synchronized (mAuthorities) { @@ -905,7 +993,7 @@ public class SyncStorageEngine extends Handler { AuthorityInfo authority = getAuthorityLocked(op.account, op.userId, op.authority, "deleteFromPending"); if (authority != null) { - if (DEBUG) Log.v(TAG, "removing - " + authority); + if (DEBUG) Log.v(TAG, "removing - " + authority.toString()); final int N = mPendingOperations.size(); boolean morePending = false; for (int i=0; i<N; i++) { @@ -1238,17 +1326,27 @@ public class SyncStorageEngine extends Handler { } /** - * Return an array of the current authorities. Note - * that the objects inside the array are the real, live objects, - * so be careful what you do with them. + * Return a copy of the specified authority with the corresponding sync status */ - public ArrayList<AuthorityInfo> getAuthorities() { + public Pair<AuthorityInfo, SyncStatusInfo> getCopyOfAuthorityWithSyncStatus( + Account account, int userId, String authority) { synchronized (mAuthorities) { - final int N = mAuthorities.size(); - ArrayList<AuthorityInfo> infos = new ArrayList<AuthorityInfo>(N); - for (int i=0; i<N; i++) { - // Make deep copy because AuthorityInfo syncs are liable to change. - infos.add(new AuthorityInfo(mAuthorities.valueAt(i))); + AuthorityInfo authorityInfo = getOrCreateAuthorityLocked(account, userId, authority, + -1 /* assign a new identifier if creating a new authority */, + true /* write to storage if this results in a change */); + return createCopyPairOfAuthorityWithSyncStatusLocked(authorityInfo); + } + } + + /** + * Return a copy of all authorities with their corresponding sync status + */ + public ArrayList<Pair<AuthorityInfo, SyncStatusInfo>> getCopyOfAllAuthoritiesWithSyncStatus() { + synchronized (mAuthorities) { + ArrayList<Pair<AuthorityInfo, SyncStatusInfo>> infos = + new ArrayList<Pair<AuthorityInfo, SyncStatusInfo>>(mAuthorities.size()); + for (int i = 0; i < mAuthorities.size(); i++) { + infos.add(createCopyPairOfAuthorityWithSyncStatusLocked(mAuthorities.valueAt(i))); } return infos; } @@ -1259,12 +1357,12 @@ public class SyncStorageEngine extends Handler { * * @param account the account we want to check * @param authority the authority whose row should be selected - * @return the SyncStatusInfo for the authority + * @return the SyncStatusInfo for the authority or null if none found. */ public SyncStatusInfo getStatusByAccountAndAuthority(Account account, int userId, String authority) { if (account == null || authority == null) { - throw new IllegalArgumentException(); + return null; } synchronized (mAuthorities) { final int N = mSyncStatus.size(); @@ -1337,6 +1435,12 @@ public class SyncStorageEngine extends Handler { } } + private Pair<AuthorityInfo, SyncStatusInfo> createCopyPairOfAuthorityWithSyncStatusLocked( + AuthorityInfo authorityInfo) { + SyncStatusInfo syncStatusInfo = getOrCreateSyncStatusLocked(authorityInfo.ident); + return Pair.create(new AuthorityInfo(authorityInfo), new SyncStatusInfo(syncStatusInfo)); + } + private int getCurrentDayLocked() { mCal.setTimeInMillis(System.currentTimeMillis()); final int dayOfYear = mCal.get(Calendar.DAY_OF_YEAR); @@ -1382,6 +1486,65 @@ public class SyncStorageEngine extends Handler { return authority; } + /** + * Retrieve an authority, returning null if one does not exist. + * + * @param service The service name used for this sync. + * @param userId The user for whom this sync is scheduled. + * @param tag If non-null, this will be used in a log message if the + * requested authority does not exist. + */ + private AuthorityInfo getAuthorityLocked(ComponentName service, int userId, String tag) { + AuthorityInfo authority = mServices.get(service).get(userId); + if (authority == null) { + if (tag != null) { + if (DEBUG) { + Log.v(TAG, tag + " No authority info found for " + service + " for user " + + userId); + } + } + return null; + } + return authority; + } + + /** + * @param cname identifier for the service. + * @param userId for the syncs corresponding to this authority. + * @param ident unique identifier for authority. -1 for none. + * @param doWrite if true, update the accounts.xml file on the disk. + * @return the authority that corresponds to the provided sync service, creating it if none + * exists. + */ + private AuthorityInfo getOrCreateAuthorityLocked(ComponentName cname, int userId, int ident, + boolean doWrite) { + SparseArray<AuthorityInfo> aInfo = mServices.get(cname); + if (aInfo == null) { + aInfo = new SparseArray<AuthorityInfo>(); + mServices.put(cname, aInfo); + } + AuthorityInfo authority = aInfo.get(userId); + if (authority == null) { + if (ident < 0) { + ident = mNextAuthorityId; + mNextAuthorityId++; + doWrite = true; + } + if (DEBUG) { + Log.v(TAG, "created a new AuthorityInfo for " + cname.getPackageName() + + ", " + cname.getClassName() + + ", user: " + userId); + } + authority = new AuthorityInfo(cname, userId, ident); + aInfo.put(userId, authority); + mAuthorities.put(ident, authority); + if (doWrite) { + writeAccountInfoLocked(); + } + } + return authority; + } + private AuthorityInfo getOrCreateAuthorityLocked(Account accountName, int userId, String authorityName, int ident, boolean doWrite) { AccountAndUser au = new AccountAndUser(accountName, userId); @@ -1427,9 +1590,28 @@ public class SyncStorageEngine extends Handler { } } - public SyncStatusInfo getOrCreateSyncStatus(AuthorityInfo authority) { + /** + * Updates (in a synchronized way) the periodic sync time of the specified + * authority id and target periodic sync + */ + public void setPeriodicSyncTime( + int authorityId, PeriodicSync targetPeriodicSync, long when) { + boolean found = false; + final AuthorityInfo authorityInfo; synchronized (mAuthorities) { - return getOrCreateSyncStatusLocked(authority.ident); + authorityInfo = mAuthorities.get(authorityId); + for (int i = 0; i < authorityInfo.periodicSyncs.size(); i++) { + PeriodicSync periodicSync = authorityInfo.periodicSyncs.get(i); + if (targetPeriodicSync.equals(periodicSync)) { + mSyncStatus.get(authorityId).setPeriodicSyncTime(i, when); + found = true; + break; + } + } + } + if (!found) { + Log.w(TAG, "Ignoring setPeriodicSyncTime request for a sync that does not exist. " + + "Authority: " + authorityInfo.authority); } } @@ -1464,6 +1646,7 @@ public class SyncStorageEngine extends Handler { synchronized (mAuthorities) { mAuthorities.clear(); mAccounts.clear(); + mServices.clear(); mPendingOperations.clear(); mSyncStatus.clear(); mSyncHistory.clear(); @@ -1488,7 +1671,9 @@ public class SyncStorageEngine extends Handler { FileInputStream fis = null; try { fis = mAccountInfoFile.openRead(); - if (DEBUG_FILE) Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile()); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Reading " + mAccountInfoFile.getBaseFile()); + } XmlPullParser parser = Xml.newPullParser(); parser.setInput(fis, null); int eventType = parser.getEventType(); @@ -1525,7 +1710,7 @@ public class SyncStorageEngine extends Handler { mMasterSyncAutomatically.put(0, listen == null || Boolean.parseBoolean(listen)); eventType = parser.next(); AuthorityInfo authority = null; - Pair<Bundle, Long> periodicSync = null; + PeriodicSync periodicSync = null; do { if (eventType == XmlPullParser.START_TAG) { tagName = parser.getName(); @@ -1545,7 +1730,7 @@ public class SyncStorageEngine extends Handler { } } else if (parser.getDepth() == 4 && periodicSync != null) { if ("extra".equals(tagName)) { - parseExtra(parser, periodicSync); + parseExtra(parser, periodicSync.extras); } } } @@ -1573,6 +1758,20 @@ public class SyncStorageEngine extends Handler { } /** + * Ensure the old pending.bin is deleted, as it has been changed to pending.xml. + * pending.xml was used starting in KLP. + * @param syncDir directory where the sync files are located. + */ + private void maybeDeleteLegacyPendingInfoLocked(File syncDir) { + File file = new File(syncDir, "pending.bin"); + if (!file.exists()) { + return; + } else { + file.delete(); + } + } + + /** * some authority names have changed. copy over their settings and delete the old ones * @return true if a change was made */ @@ -1639,8 +1838,7 @@ public class SyncStorageEngine extends Handler { AuthorityInfo authority = null; int id = -1; try { - id = Integer.parseInt(parser.getAttributeValue( - null, "id")); + id = Integer.parseInt(parser.getAttributeValue(null, "id")); } catch (NumberFormatException e) { Log.e(TAG, "error parsing the id of the authority", e); } catch (NullPointerException e) { @@ -1653,24 +1851,36 @@ public class SyncStorageEngine extends Handler { String accountName = parser.getAttributeValue(null, "account"); String accountType = parser.getAttributeValue(null, "type"); String user = parser.getAttributeValue(null, XML_ATTR_USER); + String packageName = parser.getAttributeValue(null, "package"); + String className = parser.getAttributeValue(null, "class"); int userId = user == null ? 0 : Integer.parseInt(user); if (accountType == null) { accountType = "com.google"; syncable = "unknown"; } authority = mAuthorities.get(id); - if (DEBUG_FILE) Log.v(TAG, "Adding authority: account=" - + accountName + " auth=" + authorityName - + " user=" + userId - + " enabled=" + enabled - + " syncable=" + syncable); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Adding authority: account=" + + accountName + " auth=" + authorityName + + " user=" + userId + + " enabled=" + enabled + + " syncable=" + syncable); + } if (authority == null) { - if (DEBUG_FILE) Log.v(TAG, "Creating entry"); - authority = getOrCreateAuthorityLocked( - new Account(accountName, accountType), userId, authorityName, id, false); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Creating entry"); + } + if (accountName != null && accountType != null) { + authority = getOrCreateAuthorityLocked( + new Account(accountName, accountType), userId, authorityName, id, + false); + } else { + authority = getOrCreateAuthorityLocked( + new ComponentName(packageName, className), userId, id, false); + } // If the version is 0 then we are upgrading from a file format that did not // know about periodic syncs. In that case don't clear the list since we - // want the default, which is a daily periodioc sync. + // want the default, which is a daily periodic sync. // Otherwise clear out this default list since we will populate it later with // the periodic sync descriptions that are read from the configuration file. if (version > 0) { @@ -1692,14 +1902,18 @@ public class SyncStorageEngine extends Handler { + " syncable=" + syncable); } } - return authority; } - private Pair<Bundle, Long> parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) { - Bundle extras = new Bundle(); + /** + * Parse a periodic sync from accounts.xml. Sets the bundle to be empty. + */ + private PeriodicSync parsePeriodicSync(XmlPullParser parser, AuthorityInfo authority) { + Bundle extras = new Bundle(); // Gets filled in later. String periodValue = parser.getAttributeValue(null, "period"); + String flexValue = parser.getAttributeValue(null, "flex"); final long period; + long flextime; try { period = Long.parseLong(periodValue); } catch (NumberFormatException e) { @@ -1709,14 +1923,24 @@ public class SyncStorageEngine extends Handler { Log.e(TAG, "the period of a periodic sync is null", e); return null; } - final Pair<Bundle, Long> periodicSync = Pair.create(extras, period); + try { + flextime = Long.parseLong(flexValue); + } catch (NumberFormatException e) { + Log.e(TAG, "Error formatting value parsed for periodic sync flex: " + flexValue); + flextime = calculateDefaultFlexTime(period); + } catch (NullPointerException expected) { + flextime = calculateDefaultFlexTime(period); + Log.d(TAG, "No flex time specified for this sync, using a default. period: " + + period + " flex: " + flextime); + } + final PeriodicSync periodicSync = + new PeriodicSync(authority.account, authority.authority, extras, + period, flextime); authority.periodicSyncs.add(periodicSync); - return periodicSync; } - private void parseExtra(XmlPullParser parser, Pair<Bundle, Long> periodicSync) { - final Bundle extras = periodicSync.first; + private void parseExtra(XmlPullParser parser, Bundle extras) { String name = parser.getAttributeValue(null, "name"); String type = parser.getAttributeValue(null, "type"); String value1 = parser.getAttributeValue(null, "value1"); @@ -1749,7 +1973,9 @@ public class SyncStorageEngine extends Handler { * Write all account information to the account file. */ private void writeAccountInfoLocked() { - if (DEBUG_FILE) Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile()); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Writing new " + mAccountInfoFile.getBaseFile()); + } FileOutputStream fos = null; try { @@ -1776,62 +2002,37 @@ public class SyncStorageEngine extends Handler { } final int N = mAuthorities.size(); - for (int i=0; i<N; i++) { + for (int i = 0; i < N; i++) { AuthorityInfo authority = mAuthorities.valueAt(i); out.startTag(null, "authority"); out.attribute(null, "id", Integer.toString(authority.ident)); - out.attribute(null, "account", authority.account.name); out.attribute(null, XML_ATTR_USER, Integer.toString(authority.userId)); - out.attribute(null, "type", authority.account.type); - out.attribute(null, "authority", authority.authority); out.attribute(null, XML_ATTR_ENABLED, Boolean.toString(authority.enabled)); + if (authority.service == null) { + out.attribute(null, "account", authority.account.name); + out.attribute(null, "type", authority.account.type); + out.attribute(null, "authority", authority.authority); + } else { + out.attribute(null, "package", authority.service.getPackageName()); + out.attribute(null, "class", authority.service.getClassName()); + } if (authority.syncable < 0) { out.attribute(null, "syncable", "unknown"); } else { out.attribute(null, "syncable", Boolean.toString(authority.syncable != 0)); } - for (Pair<Bundle, Long> periodicSync : authority.periodicSyncs) { + for (PeriodicSync periodicSync : authority.periodicSyncs) { out.startTag(null, "periodicSync"); - out.attribute(null, "period", Long.toString(periodicSync.second)); - final Bundle extras = periodicSync.first; - for (String key : extras.keySet()) { - out.startTag(null, "extra"); - out.attribute(null, "name", key); - final Object value = extras.get(key); - if (value instanceof Long) { - out.attribute(null, "type", "long"); - out.attribute(null, "value1", value.toString()); - } else if (value instanceof Integer) { - out.attribute(null, "type", "integer"); - out.attribute(null, "value1", value.toString()); - } else if (value instanceof Boolean) { - out.attribute(null, "type", "boolean"); - out.attribute(null, "value1", value.toString()); - } else if (value instanceof Float) { - out.attribute(null, "type", "float"); - out.attribute(null, "value1", value.toString()); - } else if (value instanceof Double) { - out.attribute(null, "type", "double"); - out.attribute(null, "value1", value.toString()); - } else if (value instanceof String) { - out.attribute(null, "type", "string"); - out.attribute(null, "value1", value.toString()); - } else if (value instanceof Account) { - out.attribute(null, "type", "account"); - out.attribute(null, "value1", ((Account)value).name); - out.attribute(null, "value2", ((Account)value).type); - } - out.endTag(null, "extra"); - } + out.attribute(null, "period", Long.toString(periodicSync.period)); + out.attribute(null, "flex", Long.toString(periodicSync.flexTime)); + final Bundle extras = periodicSync.extras; + extrasToXml(out, extras); out.endTag(null, "periodicSync"); } out.endTag(null, "authority"); } - out.endTag(null, "accounts"); - out.endDocument(); - mAccountInfoFile.finishWrite(fos); } catch (java.io.IOException e1) { Log.w(TAG, "Error writing accounts", e1); @@ -1872,7 +2073,9 @@ public class SyncStorageEngine extends Handler { final boolean hasType = db.getVersion() >= 11; // Copy in all of the status information, as well as accounts. - if (DEBUG_FILE) Log.v(TAG, "Reading legacy sync accounts db"); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Reading legacy sync accounts db"); + } SQLiteQueryBuilder qb = new SQLiteQueryBuilder(); qb.setTables("stats, status"); HashMap<String,String> map = new HashMap<String,String>(); @@ -1982,7 +2185,9 @@ public class SyncStorageEngine extends Handler { * Read all sync status back in to the initial engine state. */ private void readStatusLocked() { - if (DEBUG_FILE) Log.v(TAG, "Reading " + mStatusFile.getBaseFile()); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Reading " + mStatusFile.getBaseFile()); + } try { byte[] data = mStatusFile.readFully(); Parcel in = Parcel.obtain(); @@ -1994,8 +2199,10 @@ public class SyncStorageEngine extends Handler { SyncStatusInfo status = new SyncStatusInfo(in); if (mAuthorities.indexOfKey(status.authorityId) >= 0) { status.pending = false; - if (DEBUG_FILE) Log.v(TAG, "Adding status for id " - + status.authorityId); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Adding status for id " + + status.authorityId); + } mSyncStatus.put(status.authorityId, status); } } else { @@ -2013,7 +2220,9 @@ public class SyncStorageEngine extends Handler { * Write all sync status to the sync status file. */ private void writeStatusLocked() { - if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatusFile.getBaseFile()); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Writing new " + mStatusFile.getBaseFile()); + } // The file is being written, so we don't need to have a scheduled // write until the next change. @@ -2044,74 +2253,109 @@ public class SyncStorageEngine extends Handler { public static final int PENDING_OPERATION_VERSION = 3; - /** - * Read all pending operations back in to the initial engine state. - */ + /** Read all pending operations back in to the initial engine state. */ private void readPendingOperationsLocked() { - if (DEBUG_FILE) Log.v(TAG, "Reading " + mPendingFile.getBaseFile()); + FileInputStream fis = null; + if (!mPendingFile.getBaseFile().exists()) { + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG_FILE, "No pending operation file."); + return; + } + } try { - byte[] data = mPendingFile.readFully(); - Parcel in = Parcel.obtain(); - in.unmarshall(data, 0, data.length); - in.setDataPosition(0); - final int SIZE = in.dataSize(); - while (in.dataPosition() < SIZE) { - int version = in.readInt(); - if (version != PENDING_OPERATION_VERSION && version != 1) { - Log.w(TAG, "Unknown pending operation version " - + version + "; dropping all ops"); - break; - } - int authorityId = in.readInt(); - int syncSource = in.readInt(); - byte[] flatExtras = in.createByteArray(); - boolean expedited; - if (version == PENDING_OPERATION_VERSION) { - expedited = in.readInt() != 0; - } else { - expedited = false; - } - int reason = in.readInt(); - AuthorityInfo authority = mAuthorities.get(authorityId); - if (authority != null) { - Bundle extras; - if (flatExtras != null) { - extras = unflattenBundle(flatExtras); - } else { - // if we are unable to parse the extras for whatever reason convert this - // to a regular sync by creating an empty extras - extras = new Bundle(); + fis = mPendingFile.openRead(); + XmlPullParser parser; + parser = Xml.newPullParser(); + parser.setInput(fis, null); + + int eventType = parser.getEventType(); + while (eventType != XmlPullParser.START_TAG && + eventType != XmlPullParser.END_DOCUMENT) { + eventType = parser.next(); + } + if (eventType == XmlPullParser.END_DOCUMENT) return; // Nothing to read. + + String tagName = parser.getName(); + do { + PendingOperation pop = null; + if (eventType == XmlPullParser.START_TAG) { + try { + tagName = parser.getName(); + if (parser.getDepth() == 1 && "op".equals(tagName)) { + // Verify version. + String versionString = + parser.getAttributeValue(null, XML_ATTR_VERSION); + if (versionString == null || + Integer.parseInt(versionString) != PENDING_OPERATION_VERSION) { + Log.w(TAG, "Unknown pending operation version " + versionString); + throw new java.io.IOException("Unknown version."); + } + int authorityId = Integer.valueOf(parser.getAttributeValue( + null, XML_ATTR_AUTHORITYID)); + boolean expedited = Boolean.valueOf(parser.getAttributeValue( + null, XML_ATTR_EXPEDITED)); + int syncSource = Integer.valueOf(parser.getAttributeValue( + null, XML_ATTR_SOURCE)); + int reason = Integer.valueOf(parser.getAttributeValue( + null, XML_ATTR_REASON)); + AuthorityInfo authority = mAuthorities.get(authorityId); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG_FILE, authorityId + " " + expedited + " " + syncSource + " " + + reason); + } + if (authority != null) { + pop = new PendingOperation( + authority.account, authority.userId, reason, + syncSource, authority.authority, new Bundle(), + expedited); + pop.flatExtras = null; // No longer used. + mPendingOperations.add(pop); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG_FILE, "Adding pending op: " + + pop.authority + + " src=" + pop.syncSource + + " reason=" + pop.reason + + " expedited=" + pop.expedited); + } + } else { + // Skip non-existent authority. + pop = null; + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG_FILE, "No authority found for " + authorityId + + ", skipping"); + } + } + } else if (parser.getDepth() == 2 && + pop != null && + "extra".equals(tagName)) { + parseExtra(parser, pop.extras); + } + } catch (NumberFormatException e) { + Log.d(TAG, "Invalid data in xml file.", e); } - PendingOperation op = new PendingOperation( - authority.account, authority.userId, reason, syncSource, - authority.authority, extras, expedited); - op.authorityId = authorityId; - op.flatExtras = flatExtras; - if (DEBUG_FILE) Log.v(TAG, "Adding pending op: account=" + op.account - + " auth=" + op.authority - + " src=" + op.syncSource - + " reason=" + op.reason - + " expedited=" + op.expedited - + " extras=" + op.extras); - mPendingOperations.add(op); } - } + eventType = parser.next(); + } while(eventType != XmlPullParser.END_DOCUMENT); } catch (java.io.IOException e) { - Log.i(TAG, "No initial pending operations"); + Log.w(TAG_FILE, "Error reading pending data.", e); + } catch (XmlPullParserException e) { + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.w(TAG_FILE, "Error parsing pending ops xml.", e); + } + } finally { + if (fis != null) { + try { + fis.close(); + } catch (java.io.IOException e1) {} + } } } - private void writePendingOperationLocked(PendingOperation op, Parcel out) { - out.writeInt(PENDING_OPERATION_VERSION); - out.writeInt(op.authorityId); - out.writeInt(op.syncSource); - if (op.flatExtras == null && op.extras != null) { - op.flatExtras = flattenBundle(op.extras); - } - out.writeByteArray(op.flatExtras); - out.writeInt(op.expedited ? 1 : 0); - out.writeInt(op.reason); - } + private static final String XML_ATTR_AUTHORITYID = "authority_id"; + private static final String XML_ATTR_SOURCE = "source"; + private static final String XML_ATTR_EXPEDITED = "expedited"; + private static final String XML_ATTR_REASON = "reason"; + private static final String XML_ATTR_VERSION = "version"; /** * Write all currently pending ops to the pending ops file. @@ -2121,22 +2365,24 @@ public class SyncStorageEngine extends Handler { FileOutputStream fos = null; try { if (N == 0) { - if (DEBUG_FILE) Log.v(TAG, "Truncating " + mPendingFile.getBaseFile()); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG_FILE, "Truncating " + mPendingFile.getBaseFile()); + } mPendingFile.truncate(); return; } - - if (DEBUG_FILE) Log.v(TAG, "Writing new " + mPendingFile.getBaseFile()); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG_FILE, "Writing new " + mPendingFile.getBaseFile()); + } fos = mPendingFile.startWrite(); + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, "utf-8"); - Parcel out = Parcel.obtain(); - for (int i=0; i<N; i++) { - PendingOperation op = mPendingOperations.get(i); - writePendingOperationLocked(op, out); + for (int i = 0; i < N; i++) { + PendingOperation pop = mPendingOperations.get(i); + writePendingOperationLocked(pop, out); } - fos.write(out.marshall()); - out.recycle(); - + out.endDocument(); mPendingFile.finishWrite(fos); } catch (java.io.IOException e1) { Log.w(TAG, "Error writing pending operations", e1); @@ -2146,33 +2392,54 @@ public class SyncStorageEngine extends Handler { } } + /** Write all currently pending ops to the pending ops file. */ + private void writePendingOperationLocked(PendingOperation pop, XmlSerializer out) + throws IOException { + // Pending operation. + out.startTag(null, "op"); + + out.attribute(null, XML_ATTR_VERSION, Integer.toString(PENDING_OPERATION_VERSION)); + out.attribute(null, XML_ATTR_AUTHORITYID, Integer.toString(pop.authorityId)); + out.attribute(null, XML_ATTR_SOURCE, Integer.toString(pop.syncSource)); + out.attribute(null, XML_ATTR_EXPEDITED, Boolean.toString(pop.expedited)); + out.attribute(null, XML_ATTR_REASON, Integer.toString(pop.reason)); + extrasToXml(out, pop.extras); + + out.endTag(null, "op"); + } + /** * Append the given operation to the pending ops file; if unable to, * write all pending ops. */ private void appendPendingOperationLocked(PendingOperation op) { - if (DEBUG_FILE) Log.v(TAG, "Appending to " + mPendingFile.getBaseFile()); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Appending to " + mPendingFile.getBaseFile()); + } FileOutputStream fos = null; try { fos = mPendingFile.openAppend(); } catch (java.io.IOException e) { - if (DEBUG_FILE) Log.v(TAG, "Failed append; writing full file"); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Failed append; writing full file"); + } writePendingOperationsLocked(); return; } try { - Parcel out = Parcel.obtain(); + XmlSerializer out = new FastXmlSerializer(); + out.setOutput(fos, "utf-8"); writePendingOperationLocked(op, out); - fos.write(out.marshall()); - out.recycle(); + out.endDocument(); + mPendingFile.finishWrite(fos); } catch (java.io.IOException e1) { - Log.w(TAG, "Error writing pending operations", e1); + Log.w(TAG, "Error writing appending operation", e1); + mPendingFile.failWrite(fos); } finally { try { fos.close(); - } catch (java.io.IOException e2) { - } + } catch (IOException e) {} } } @@ -2205,6 +2472,38 @@ public class SyncStorageEngine extends Handler { return bundle; } + private void extrasToXml(XmlSerializer out, Bundle extras) throws java.io.IOException { + for (String key : extras.keySet()) { + out.startTag(null, "extra"); + out.attribute(null, "name", key); + final Object value = extras.get(key); + if (value instanceof Long) { + out.attribute(null, "type", "long"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof Integer) { + out.attribute(null, "type", "integer"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof Boolean) { + out.attribute(null, "type", "boolean"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof Float) { + out.attribute(null, "type", "float"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof Double) { + out.attribute(null, "type", "double"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof String) { + out.attribute(null, "type", "string"); + out.attribute(null, "value1", value.toString()); + } else if (value instanceof Account) { + out.attribute(null, "type", "account"); + out.attribute(null, "value1", ((Account)value).name); + out.attribute(null, "value2", ((Account)value).type); + } + out.endTag(null, "extra"); + } + } + private void requestSync(Account account, int userId, int reason, String authority, Bundle extras) { // If this is happening in the system process, then call the syncrequest listener @@ -2265,7 +2564,9 @@ public class SyncStorageEngine extends Handler { * Write all sync statistics to the sync status file. */ private void writeStatisticsLocked() { - if (DEBUG_FILE) Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile()); + if (Log.isLoggable(TAG_FILE, Log.VERBOSE)) { + Log.v(TAG, "Writing new " + mStatisticsFile.getBaseFile()); + } // The file is being written, so we don't need to have a scheduled // write until the next change. @@ -2300,4 +2601,18 @@ public class SyncStorageEngine extends Handler { } } } + + /** + * Dump state of PendingOperations. + */ + public void dumpPendingOperations(StringBuilder sb) { + sb.append("Pending Ops: ").append(mPendingOperations.size()).append(" operation(s)\n"); + for (PendingOperation pop : mPendingOperations) { + sb.append("(" + pop.account) + .append(", u" + pop.userId) + .append(", " + pop.authority) + .append(", " + pop.extras) + .append(")\n"); + } + } } diff --git a/services/java/com/android/server/display/DisplayDeviceInfo.java b/services/java/com/android/server/display/DisplayDeviceInfo.java index 247d8a049270..11c5d879b498 100644 --- a/services/java/com/android/server/display/DisplayDeviceInfo.java +++ b/services/java/com/android/server/display/DisplayDeviceInfo.java @@ -61,6 +61,23 @@ final class DisplayDeviceInfo { public static final int FLAG_SUPPORTS_PROTECTED_BUFFERS = 1 << 3; /** + * Flag: Indicates that the display device is owned by a particular application + * and that no other application should be able to interact with it. + */ + public static final int FLAG_PRIVATE = 1 << 4; + + /** + * Flag: Indicates that the display device is not blanked automatically by + * the power manager. + */ + public static final int FLAG_NEVER_BLANK = 1 << 5; + + /** + * Flag: Indicates that the display is suitable for presentations. + */ + public static final int FLAG_PRESENTATION = 1 << 6; + + /** * Touch attachment: Display does not receive touch. */ public static final int TOUCH_NONE = 0; @@ -150,6 +167,23 @@ final class DisplayDeviceInfo { */ public String address; + /** + * The UID of the application that owns this display, or zero if it is owned by the system. + * <p> + * If the display is private, then only the owner can use it. + * </p> + */ + public int ownerUid; + + /** + * The package name of the application that owns this display, or null if it is + * owned by the system. + * <p> + * If the display is private, then only the owner can use it. + * </p> + */ + public String ownerPackageName; + public void setAssumedDensityForExternalDisplay(int width, int height) { densityDpi = Math.min(width, height) * DisplayMetrics.DENSITY_XHIGH / 1080; // Technically, these values should be smaller than the apparent density @@ -176,7 +210,9 @@ final class DisplayDeviceInfo { && touch == other.touch && rotation == other.rotation && type == other.type - && Objects.equal(address, other.address); + && Objects.equal(address, other.address) + && ownerUid == other.ownerUid + && Objects.equal(ownerPackageName, other.ownerPackageName); } @Override @@ -197,19 +233,32 @@ final class DisplayDeviceInfo { rotation = other.rotation; type = other.type; address = other.address; + ownerUid = other.ownerUid; + ownerPackageName = other.ownerPackageName; } // For debugging purposes @Override public String toString() { - return "DisplayDeviceInfo{\"" + name + "\": " + width + " x " + height + ", " - + refreshRate + " fps, " - + "density " + densityDpi + ", " + xDpi + " x " + yDpi + " dpi" - + ", touch " + touchToString(touch) + flagsToString(flags) - + ", rotation " + rotation - + ", type " + Display.typeToString(type) - + ", address " + address - + "}"; + StringBuilder sb = new StringBuilder(); + sb.append("DisplayDeviceInfo{\""); + sb.append(name).append("\": ").append(width).append(" x ").append(height); + sb.append(", ").append(refreshRate).append(" fps, "); + sb.append("density ").append(densityDpi); + sb.append(", ").append(xDpi).append(" x ").append(yDpi).append(" dpi"); + sb.append(", touch ").append(touchToString(touch)); + sb.append(", rotation ").append(rotation); + sb.append(", type ").append(Display.typeToString(type)); + if (address != null) { + sb.append(", address ").append(address); + } + if (ownerUid != 0 || ownerPackageName != null) { + sb.append(", owner ").append(ownerPackageName); + sb.append(" (uid ").append(ownerUid).append(")"); + } + sb.append(flagsToString(flags)); + sb.append("}"); + return sb.toString(); } private static String touchToString(int touch) { @@ -239,6 +288,15 @@ final class DisplayDeviceInfo { if ((flags & FLAG_SUPPORTS_PROTECTED_BUFFERS) != 0) { msg.append(", FLAG_SUPPORTS_PROTECTED_BUFFERS"); } + if ((flags & FLAG_PRIVATE) != 0) { + msg.append(", FLAG_PRIVATE"); + } + if ((flags & FLAG_NEVER_BLANK) != 0) { + msg.append(", FLAG_NEVER_BLANK"); + } + if ((flags & FLAG_PRESENTATION) != 0) { + msg.append(", FLAG_PRESENTATION"); + } return msg.toString(); } } diff --git a/services/java/com/android/server/display/DisplayManagerService.java b/services/java/com/android/server/display/DisplayManagerService.java index 17b066272262..249c8b004e64 100644 --- a/services/java/com/android/server/display/DisplayManagerService.java +++ b/services/java/com/android/server/display/DisplayManagerService.java @@ -21,6 +21,7 @@ import com.android.internal.util.IndentingPrintWriter; import android.Manifest; import android.content.Context; import android.content.pm.PackageManager; +import android.hardware.display.DisplayManager; import android.hardware.display.DisplayManagerGlobal; import android.hardware.display.IDisplayManager; import android.hardware.display.IDisplayManagerCallback; @@ -33,14 +34,19 @@ import android.os.Message; import android.os.RemoteException; import android.os.SystemClock; import android.os.SystemProperties; +import android.text.TextUtils; import android.util.Slog; import android.util.SparseArray; import android.view.Display; import android.view.DisplayInfo; +import android.view.Surface; + +import com.android.server.UiThread; import java.io.FileDescriptor; import java.io.PrintWriter; import java.util.ArrayList; +import java.util.Arrays; import java.util.concurrent.CopyOnWriteArrayList; /** @@ -148,9 +154,6 @@ public final class DisplayManagerService extends IDisplayManager.Stub { // List of all currently connected display devices. private final ArrayList<DisplayDevice> mDisplayDevices = new ArrayList<DisplayDevice>(); - // List of all removed display devices. - private final ArrayList<DisplayDevice> mRemovedDisplayDevices = new ArrayList<DisplayDevice>(); - // List of all logical displays indexed by logical display id. private final SparseArray<LogicalDisplay> mLogicalDisplays = new SparseArray<LogicalDisplay>(); @@ -170,6 +173,9 @@ public final class DisplayManagerService extends IDisplayManager.Stub { // The Wifi display adapter, or null if not registered. private WifiDisplayAdapter mWifiDisplayAdapter; + // The virtual display adapter, or null if not registered. + private VirtualDisplayAdapter mVirtualDisplayAdapter; + // Viewports of the default display and the display that should receive touch // input from an external source. Used by the input system. private final DisplayViewport mDefaultViewport = new DisplayViewport(); @@ -190,12 +196,12 @@ public final class DisplayManagerService extends IDisplayManager.Stub { private final DisplayViewport mTempDefaultViewport = new DisplayViewport(); private final DisplayViewport mTempExternalTouchViewport = new DisplayViewport(); - public DisplayManagerService(Context context, Handler mainHandler, Handler uiHandler) { + public DisplayManagerService(Context context, Handler mainHandler) { mContext = context; mHeadless = SystemProperties.get(SYSTEM_HEADLESS).equals("1"); mHandler = new DisplayManagerHandler(mainHandler.getLooper()); - mUiHandler = uiHandler; + mUiHandler = UiThread.getHandler(); mDisplayAdapterListener = new DisplayAdapterListener(); mSingleDisplayDemoMode = SystemProperties.getBoolean("persist.demo.singledisplay", false); @@ -305,6 +311,9 @@ public final class DisplayManagerService extends IDisplayManager.Stub { * to the display information synchronously so that applications will immediately * observe the new state. * + * NOTE: This method must be the only entry point by which the window manager + * influences the logical configuration of displays. + * * @param displayId The logical display id. * @param info The new data to be stored. */ @@ -313,9 +322,7 @@ public final class DisplayManagerService extends IDisplayManager.Stub { synchronized (mSyncRoot) { LogicalDisplay display = mLogicalDisplays.get(displayId); if (display != null) { - mTempDisplayInfo.copyFrom(display.getDisplayInfoLocked()); - display.setDisplayInfoOverrideFromWindowManagerLocked(info); - if (!mTempDisplayInfo.equals(display.getDisplayInfoLocked())) { + if (display.setDisplayInfoOverrideFromWindowManagerLocked(info)) { sendDisplayEventLocked(displayId, DisplayManagerGlobal.EVENT_DISPLAY_CHANGED); scheduleTraversalLocked(false); } @@ -324,18 +331,6 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } /** - * Sets the overscan insets for a particular display. - */ - public void setOverscan(int displayId, int left, int top, int right, int bottom) { - synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplays.get(displayId); - if (display != null) { - display.setOverscan(left, top, right, bottom); - } - } - } - - /** * Called by the window manager to perform traversals while holding a * surface flinger transaction. */ @@ -362,13 +357,7 @@ public final class DisplayManagerService extends IDisplayManager.Stub { synchronized (mSyncRoot) { if (mAllDisplayBlankStateFromPowerManager != DISPLAY_BLANK_STATE_BLANKED) { mAllDisplayBlankStateFromPowerManager = DISPLAY_BLANK_STATE_BLANKED; - - final int count = mDisplayDevices.size(); - for (int i = 0; i < count; i++) { - DisplayDevice device = mDisplayDevices.get(i); - device.blankLocked(); - } - + updateAllDisplayBlankingLocked(); scheduleTraversalLocked(false); } } @@ -381,13 +370,7 @@ public final class DisplayManagerService extends IDisplayManager.Stub { synchronized (mSyncRoot) { if (mAllDisplayBlankStateFromPowerManager != DISPLAY_BLANK_STATE_UNBLANKED) { mAllDisplayBlankStateFromPowerManager = DISPLAY_BLANK_STATE_UNBLANKED; - - final int count = mDisplayDevices.size(); - for (int i = 0; i < count; i++) { - DisplayDevice device = mDisplayDevices.get(i); - device.unblankLocked(); - } - + updateAllDisplayBlankingLocked(); scheduleTraversalLocked(false); } } @@ -402,12 +385,21 @@ public final class DisplayManagerService extends IDisplayManager.Stub { */ @Override // Binder call public DisplayInfo getDisplayInfo(int displayId) { - synchronized (mSyncRoot) { - LogicalDisplay display = mLogicalDisplays.get(displayId); - if (display != null) { - return display.getDisplayInfoLocked(); + final int callingUid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + LogicalDisplay display = mLogicalDisplays.get(displayId); + if (display != null) { + DisplayInfo info = display.getDisplayInfoLocked(); + if (info.hasAccess(callingUid)) { + return info; + } + } + return null; } - return null; + } finally { + Binder.restoreCallingIdentity(token); } } @@ -416,13 +408,27 @@ public final class DisplayManagerService extends IDisplayManager.Stub { */ @Override // Binder call public int[] getDisplayIds() { - synchronized (mSyncRoot) { - final int count = mLogicalDisplays.size(); - int[] displayIds = new int[count]; - for (int i = 0; i < count; i++) { - displayIds[i] = mLogicalDisplays.keyAt(i); + final int callingUid = Binder.getCallingUid(); + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + final int count = mLogicalDisplays.size(); + int[] displayIds = new int[count]; + int n = 0; + for (int i = 0; i < count; i++) { + LogicalDisplay display = mLogicalDisplays.valueAt(i); + DisplayInfo info = display.getDisplayInfoLocked(); + if (info.hasAccess(callingUid)) { + displayIds[n++] = mLogicalDisplays.keyAt(i); + } + } + if (n != count) { + displayIds = Arrays.copyOfRange(displayIds, 0, n); + } + return displayIds; } - return displayIds; + } finally { + Binder.restoreCallingIdentity(token); } } @@ -491,6 +497,48 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } } + @Override + public void pauseWifiDisplay() { + if (mContext.checkCallingPermission( + android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY" + + "permission to pause a wifi display session."); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + if (mWifiDisplayAdapter != null) { + mWifiDisplayAdapter.requestPauseLocked(); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + @Override + public void resumeWifiDisplay() { + if (mContext.checkCallingPermission( + android.Manifest.permission.CONFIGURE_WIFI_DISPLAY) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires CONFIGURE_WIFI_DISPLAY" + + "permission to resume a wifi display session."); + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + if (mWifiDisplayAdapter != null) { + mWifiDisplayAdapter.requestResumeLocked(); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + @Override // Binder call public void disconnectWifiDisplay() { final long token = Binder.clearCallingIdentity(); @@ -569,6 +617,114 @@ public final class DisplayManagerService extends IDisplayManager.Stub { == PackageManager.PERMISSION_GRANTED; } + @Override // Binder call + public int createVirtualDisplay(IBinder appToken, String packageName, + String name, int width, int height, int densityDpi, Surface surface, int flags) { + final int callingUid = Binder.getCallingUid(); + if (!validatePackageName(callingUid, packageName)) { + throw new SecurityException("packageName must match the calling uid"); + } + if (appToken == null) { + throw new IllegalArgumentException("appToken must not be null"); + } + if (TextUtils.isEmpty(name)) { + throw new IllegalArgumentException("name must be non-null and non-empty"); + } + if (width <= 0 || height <= 0 || densityDpi <= 0) { + throw new IllegalArgumentException("width, height, and densityDpi must be " + + "greater than 0"); + } + if (surface == null) { + throw new IllegalArgumentException("surface must not be null"); + } + if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) != 0) { + if (mContext.checkCallingPermission(android.Manifest.permission.CAPTURE_VIDEO_OUTPUT) + != PackageManager.PERMISSION_GRANTED + && mContext.checkCallingPermission( + android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires CAPTURE_VIDEO_OUTPUT or " + + "CAPTURE_SECURE_VIDEO_OUTPUT permission to create a " + + "public virtual display."); + } + } + if ((flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0) { + if (mContext.checkCallingPermission( + android.Manifest.permission.CAPTURE_SECURE_VIDEO_OUTPUT) + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Requires CAPTURE_SECURE_VIDEO_OUTPUT " + + "to create a secure virtual display."); + } + } + + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + if (mVirtualDisplayAdapter == null) { + Slog.w(TAG, "Rejecting request to create private virtual display " + + "because the virtual display adapter is not available."); + return -1; + } + + DisplayDevice device = mVirtualDisplayAdapter.createVirtualDisplayLocked( + appToken, callingUid, packageName, name, width, height, densityDpi, + surface, flags); + if (device == null) { + return -1; + } + + handleDisplayDeviceAddedLocked(device); + LogicalDisplay display = findLogicalDisplayForDeviceLocked(device); + if (display != null) { + return display.getDisplayIdLocked(); + } + + // Something weird happened and the logical display was not created. + Slog.w(TAG, "Rejecting request to create virtual display " + + "because the logical display was not created."); + mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken); + handleDisplayDeviceRemovedLocked(device); + } + } finally { + Binder.restoreCallingIdentity(token); + } + return -1; + } + + @Override // Binder call + public void releaseVirtualDisplay(IBinder appToken) { + final long token = Binder.clearCallingIdentity(); + try { + synchronized (mSyncRoot) { + if (mVirtualDisplayAdapter == null) { + return; + } + + DisplayDevice device = + mVirtualDisplayAdapter.releaseVirtualDisplayLocked(appToken); + if (device != null) { + handleDisplayDeviceRemovedLocked(device); + } + } + } finally { + Binder.restoreCallingIdentity(token); + } + } + + private boolean validatePackageName(int uid, String packageName) { + if (packageName != null) { + String[] packageNames = mContext.getPackageManager().getPackagesForUid(uid); + if (packageNames != null) { + for (String n : packageNames) { + if (n.equals(packageName)) { + return true; + } + } + } + } + return false; + } + private void registerDefaultDisplayAdapter() { // Register default display adapter. synchronized (mSyncRoot) { @@ -587,6 +743,7 @@ public final class DisplayManagerService extends IDisplayManager.Stub { if (shouldRegisterNonEssentialDisplayAdaptersLocked()) { registerOverlayDisplayAdapterLocked(); registerWifiDisplayAdapterLocked(); + registerVirtualDisplayAdapterLocked(); } } } @@ -607,6 +764,12 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } } + private void registerVirtualDisplayAdapterLocked() { + mVirtualDisplayAdapter = new VirtualDisplayAdapter( + mSyncRoot, mContext, mHandler, mDisplayAdapterListener); + registerDisplayAdapterLocked(mVirtualDisplayAdapter); + } + private boolean shouldRegisterNonEssentialDisplayAdaptersLocked() { // In safe mode, we disable non-essential display adapters to give the user // an opportunity to fix broken settings or other problems that might affect @@ -624,29 +787,23 @@ public final class DisplayManagerService extends IDisplayManager.Stub { private void handleDisplayDeviceAdded(DisplayDevice device) { synchronized (mSyncRoot) { - if (mDisplayDevices.contains(device)) { - Slog.w(TAG, "Attempted to add already added display device: " - + device.getDisplayDeviceInfoLocked()); - return; - } + handleDisplayDeviceAddedLocked(device); + } + } - Slog.i(TAG, "Display device added: " + device.getDisplayDeviceInfoLocked()); + private void handleDisplayDeviceAddedLocked(DisplayDevice device) { + if (mDisplayDevices.contains(device)) { + Slog.w(TAG, "Attempted to add already added display device: " + + device.getDisplayDeviceInfoLocked()); + return; + } - mDisplayDevices.add(device); - addLogicalDisplayLocked(device); - scheduleTraversalLocked(false); + Slog.i(TAG, "Display device added: " + device.getDisplayDeviceInfoLocked()); - // Blank or unblank the display immediately to match the state requested - // by the power manager (if known). - switch (mAllDisplayBlankStateFromPowerManager) { - case DISPLAY_BLANK_STATE_BLANKED: - device.blankLocked(); - break; - case DISPLAY_BLANK_STATE_UNBLANKED: - device.unblankLocked(); - break; - } - } + mDisplayDevices.add(device); + addLogicalDisplayLocked(device); + updateDisplayBlankingLocked(device); + scheduleTraversalLocked(false); } private void handleDisplayDeviceChanged(DisplayDevice device) { @@ -668,17 +825,43 @@ public final class DisplayManagerService extends IDisplayManager.Stub { private void handleDisplayDeviceRemoved(DisplayDevice device) { synchronized (mSyncRoot) { - if (!mDisplayDevices.remove(device)) { - Slog.w(TAG, "Attempted to remove non-existent display device: " - + device.getDisplayDeviceInfoLocked()); - return; - } + handleDisplayDeviceRemovedLocked(device); + } + } + private void handleDisplayDeviceRemovedLocked(DisplayDevice device) { + if (!mDisplayDevices.remove(device)) { + Slog.w(TAG, "Attempted to remove non-existent display device: " + + device.getDisplayDeviceInfoLocked()); + return; + } - Slog.i(TAG, "Display device removed: " + device.getDisplayDeviceInfoLocked()); + Slog.i(TAG, "Display device removed: " + device.getDisplayDeviceInfoLocked()); - mRemovedDisplayDevices.add(device); - updateLogicalDisplaysLocked(); - scheduleTraversalLocked(false); + updateLogicalDisplaysLocked(); + scheduleTraversalLocked(false); + } + + private void updateAllDisplayBlankingLocked() { + final int count = mDisplayDevices.size(); + for (int i = 0; i < count; i++) { + DisplayDevice device = mDisplayDevices.get(i); + updateDisplayBlankingLocked(device); + } + } + + private void updateDisplayBlankingLocked(DisplayDevice device) { + // Blank or unblank the display immediately to match the state requested + // by the power manager (if known). + DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + if ((info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0) { + switch (mAllDisplayBlankStateFromPowerManager) { + case DISPLAY_BLANK_STATE_BLANKED: + device.blankLocked(); + break; + case DISPLAY_BLANK_STATE_UNBLANKED: + device.unblankLocked(); + break; + } } } @@ -755,14 +938,6 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } private void performTraversalInTransactionLocked() { - // Perform one last traversal for each removed display device. - final int removedCount = mRemovedDisplayDevices.size(); - for (int i = 0; i < removedCount; i++) { - DisplayDevice device = mRemovedDisplayDevices.get(i); - device.performTraversalInTransactionLocked(); - } - mRemovedDisplayDevices.clear(); - // Clear all viewports before configuring displays so that we can keep // track of which ones we have configured. clearViewportsLocked(); @@ -799,6 +974,11 @@ public final class DisplayManagerService extends IDisplayManager.Stub { synchronized (mSyncRoot) { LogicalDisplay display = mLogicalDisplays.get(displayId); if (display != null && display.hasContentLocked() != hasContent) { + if (DEBUG) { + Slog.d(TAG, "Display " + displayId + " hasContent flag changed: " + + "hasContent=" + hasContent + ", inTraversal=" + inTraversal); + } + display.setHasContentLocked(hasContent); scheduleTraversalLocked(inTraversal); } @@ -811,13 +991,21 @@ public final class DisplayManagerService extends IDisplayManager.Stub { } private void configureDisplayInTransactionLocked(DisplayDevice device) { + DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); + boolean isPrivate = (info.flags & DisplayDeviceInfo.FLAG_PRIVATE) != 0; + // Find the logical display that the display device is showing. + // Private displays never mirror other displays. LogicalDisplay display = findLogicalDisplayForDeviceLocked(device); - if (display != null && !display.hasContentLocked()) { - display = null; - } - if (display == null) { - display = mLogicalDisplays.get(Display.DEFAULT_DISPLAY); + if (!isPrivate) { + if (display != null && !display.hasContentLocked()) { + // If the display does not have any content of its own, then + // automatically mirror the default logical display contents. + display = null; + } + if (display == null) { + display = mLogicalDisplays.get(Display.DEFAULT_DISPLAY); + } } // Apply the logical display configuration to the display device. @@ -827,11 +1015,11 @@ public final class DisplayManagerService extends IDisplayManager.Stub { + device.getDisplayDeviceInfoLocked()); return; } - boolean isBlanked = (mAllDisplayBlankStateFromPowerManager == DISPLAY_BLANK_STATE_BLANKED); + boolean isBlanked = (mAllDisplayBlankStateFromPowerManager == DISPLAY_BLANK_STATE_BLANKED) + && (info.flags & DisplayDeviceInfo.FLAG_NEVER_BLANK) == 0; display.configureDisplayInTransactionLocked(device, isBlanked); // Update the viewports if needed. - DisplayDeviceInfo info = device.getDisplayDeviceInfoLocked(); if (!mDefaultViewport.valid && (info.flags & DisplayDeviceInfo.FLAG_DEFAULT_DISPLAY) != 0) { setViewportLocked(mDefaultViewport, display, device); diff --git a/services/java/com/android/server/display/LocalDisplayAdapter.java b/services/java/com/android/server/display/LocalDisplayAdapter.java index 475f27b83e29..cb8f3e20d368 100644 --- a/services/java/com/android/server/display/LocalDisplayAdapter.java +++ b/services/java/com/android/server/display/LocalDisplayAdapter.java @@ -155,6 +155,7 @@ final class LocalDisplayAdapter extends DisplayAdapter { mInfo.touch = DisplayDeviceInfo.TOUCH_INTERNAL; } else { mInfo.type = Display.TYPE_HDMI; + mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION; mInfo.name = getContext().getResources().getString( com.android.internal.R.string.display_manager_hdmi_display_name); mInfo.touch = DisplayDeviceInfo.TOUCH_EXTERNAL; diff --git a/services/java/com/android/server/display/LogicalDisplay.java b/services/java/com/android/server/display/LogicalDisplay.java index 424ec36c27e9..7e357c0c3a7c 100644 --- a/services/java/com/android/server/display/LogicalDisplay.java +++ b/services/java/com/android/server/display/LogicalDisplay.java @@ -128,32 +128,24 @@ final class LogicalDisplay { * * @param info The logical display information, may be null. */ - public void setDisplayInfoOverrideFromWindowManagerLocked(DisplayInfo info) { + public boolean setDisplayInfoOverrideFromWindowManagerLocked(DisplayInfo info) { if (info != null) { if (mOverrideDisplayInfo == null) { mOverrideDisplayInfo = new DisplayInfo(info); mInfo = null; - } else if (!mOverrideDisplayInfo.equals(info)) { + return true; + } + if (!mOverrideDisplayInfo.equals(info)) { mOverrideDisplayInfo.copyFrom(info); mInfo = null; + return true; } } else if (mOverrideDisplayInfo != null) { mOverrideDisplayInfo = null; mInfo = null; + return true; } - } - - public void setOverscan(int left, int top, int right, int bottom) { - mInfo.overscanLeft = left; - mInfo.overscanTop = top; - mInfo.overscanRight = right; - mInfo.overscanBottom = bottom; - if (mOverrideDisplayInfo != null) { - mOverrideDisplayInfo.overscanLeft = left; - mOverrideDisplayInfo.overscanTop = top; - mOverrideDisplayInfo.overscanRight = right; - mOverrideDisplayInfo.overscanBottom = bottom; - } + return false; } /** @@ -202,6 +194,12 @@ final class LogicalDisplay { if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_SECURE) != 0) { mBaseDisplayInfo.flags |= Display.FLAG_SECURE; } + if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_PRIVATE) != 0) { + mBaseDisplayInfo.flags |= Display.FLAG_PRIVATE; + } + if ((deviceInfo.flags & DisplayDeviceInfo.FLAG_PRESENTATION) != 0) { + mBaseDisplayInfo.flags |= Display.FLAG_PRESENTATION; + } mBaseDisplayInfo.type = deviceInfo.type; mBaseDisplayInfo.address = deviceInfo.address; mBaseDisplayInfo.name = deviceInfo.name; @@ -218,6 +216,8 @@ final class LogicalDisplay { mBaseDisplayInfo.smallestNominalAppHeight = deviceInfo.height; mBaseDisplayInfo.largestNominalAppWidth = deviceInfo.width; mBaseDisplayInfo.largestNominalAppHeight = deviceInfo.height; + mBaseDisplayInfo.ownerUid = deviceInfo.ownerUid; + mBaseDisplayInfo.ownerPackageName = deviceInfo.ownerPackageName; mPrimaryDisplayDeviceInfo = deviceInfo; mInfo = null; diff --git a/services/java/com/android/server/display/OverlayDisplayAdapter.java b/services/java/com/android/server/display/OverlayDisplayAdapter.java index a18352ce931a..007acf716598 100644 --- a/services/java/com/android/server/display/OverlayDisplayAdapter.java +++ b/services/java/com/android/server/display/OverlayDisplayAdapter.java @@ -59,7 +59,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { private static final int MAX_HEIGHT = 4096; private static final Pattern SETTING_PATTERN = - Pattern.compile("(\\d+)x(\\d+)/(\\d+)"); + Pattern.compile("(\\d+)x(\\d+)/(\\d+)(,[a-z]+)*"); private final Handler mUiHandler; private final ArrayList<OverlayDisplayHandle> mOverlays = @@ -143,6 +143,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { int width = Integer.parseInt(matcher.group(1), 10); int height = Integer.parseInt(matcher.group(2), 10); int densityDpi = Integer.parseInt(matcher.group(3), 10); + String flagString = matcher.group(4); if (width >= MIN_WIDTH && width <= MAX_WIDTH && height >= MIN_HEIGHT && height <= MAX_HEIGHT && densityDpi >= DisplayMetrics.DENSITY_LOW @@ -152,13 +153,14 @@ final class OverlayDisplayAdapter extends DisplayAdapter { com.android.internal.R.string.display_manager_overlay_display_name, number); int gravity = chooseOverlayGravity(number); + boolean secure = flagString != null && flagString.contains(",secure"); Slog.i(TAG, "Showing overlay display device #" + number + ": name=" + name + ", width=" + width + ", height=" + height - + ", densityDpi=" + densityDpi); + + ", densityDpi=" + densityDpi + ", secure=" + secure); mOverlays.add(new OverlayDisplayHandle(name, - width, height, densityDpi, gravity)); + width, height, densityDpi, gravity, secure)); continue; } } catch (NumberFormatException ex) { @@ -190,13 +192,14 @@ final class OverlayDisplayAdapter extends DisplayAdapter { private final int mHeight; private final float mRefreshRate; private final int mDensityDpi; + private final boolean mSecure; private Surface mSurface; private SurfaceTexture mSurfaceTexture; private DisplayDeviceInfo mInfo; public OverlayDisplayDevice(IBinder displayToken, String name, - int width, int height, float refreshRate, int densityDpi, + int width, int height, float refreshRate, int densityDpi, boolean secure, SurfaceTexture surfaceTexture) { super(OverlayDisplayAdapter.this, displayToken); mName = name; @@ -204,14 +207,17 @@ final class OverlayDisplayAdapter extends DisplayAdapter { mHeight = height; mRefreshRate = refreshRate; mDensityDpi = densityDpi; + mSecure = secure; mSurfaceTexture = surfaceTexture; } - public void clearSurfaceTextureLocked() { - if (mSurfaceTexture != null) { - mSurfaceTexture = null; + public void destroyLocked() { + mSurfaceTexture = null; + if (mSurface != null) { + mSurface.release(); + mSurface = null; } - sendTraversalRequestLocked(); + SurfaceControl.destroyDisplay(getDisplayTokenLocked()); } @Override @@ -221,12 +227,6 @@ final class OverlayDisplayAdapter extends DisplayAdapter { mSurface = new Surface(mSurfaceTexture); } setSurfaceInTransactionLocked(mSurface); - } else { - setSurfaceInTransactionLocked(null); - if (mSurface != null) { - mSurface.destroy(); - mSurface = null; - } } } @@ -241,7 +241,10 @@ final class OverlayDisplayAdapter extends DisplayAdapter { mInfo.densityDpi = mDensityDpi; mInfo.xDpi = mDensityDpi; mInfo.yDpi = mDensityDpi; - mInfo.flags = 0; + mInfo.flags = DisplayDeviceInfo.FLAG_PRESENTATION; + if (mSecure) { + mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE; + } mInfo.type = Display.TYPE_OVERLAY; mInfo.touch = DisplayDeviceInfo.TOUCH_NONE; } @@ -261,17 +264,19 @@ final class OverlayDisplayAdapter extends DisplayAdapter { private final int mHeight; private final int mDensityDpi; private final int mGravity; + private final boolean mSecure; private OverlayDisplayWindow mWindow; private OverlayDisplayDevice mDevice; public OverlayDisplayHandle(String name, - int width, int height, int densityDpi, int gravity) { + int width, int height, int densityDpi, int gravity, boolean secure) { mName = name; mWidth = width; mHeight = height; mDensityDpi = densityDpi; mGravity = gravity; + mSecure = secure; mUiHandler.post(mShowRunnable); } @@ -285,9 +290,9 @@ final class OverlayDisplayAdapter extends DisplayAdapter { @Override public void onWindowCreated(SurfaceTexture surfaceTexture, float refreshRate) { synchronized (getSyncRoot()) { - IBinder displayToken = SurfaceControl.createDisplay(mName, false); + IBinder displayToken = SurfaceControl.createDisplay(mName, mSecure); mDevice = new OverlayDisplayDevice(displayToken, mName, - mWidth, mHeight, refreshRate, mDensityDpi, surfaceTexture); + mWidth, mHeight, refreshRate, mDensityDpi, mSecure, surfaceTexture); sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_ADDED); } @@ -298,7 +303,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { public void onWindowDestroyed() { synchronized (getSyncRoot()) { if (mDevice != null) { - mDevice.clearSurfaceTextureLocked(); + mDevice.destroyLocked(); sendDisplayDeviceEventLocked(mDevice, DISPLAY_DEVICE_EVENT_REMOVED); } } @@ -310,6 +315,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { pw.println(" mHeight=" + mHeight); pw.println(" mDensityDpi=" + mDensityDpi); pw.println(" mGravity=" + mGravity); + pw.println(" mSecure=" + mSecure); // Try to dump the window state. if (mWindow != null) { @@ -324,7 +330,7 @@ final class OverlayDisplayAdapter extends DisplayAdapter { @Override public void run() { OverlayDisplayWindow window = new OverlayDisplayWindow(getContext(), - mName, mWidth, mHeight, mDensityDpi, mGravity, + mName, mWidth, mHeight, mDensityDpi, mGravity, mSecure, OverlayDisplayHandle.this); window.show(); diff --git a/services/java/com/android/server/display/OverlayDisplayWindow.java b/services/java/com/android/server/display/OverlayDisplayWindow.java index a0edced4b853..f1dd60a75424 100644 --- a/services/java/com/android/server/display/OverlayDisplayWindow.java +++ b/services/java/com/android/server/display/OverlayDisplayWindow.java @@ -64,8 +64,9 @@ final class OverlayDisplayWindow implements DumpUtils.Dump { private final int mHeight; private final int mDensityDpi; private final int mGravity; + private final boolean mSecure; private final Listener mListener; - private final String mTitle; + private String mTitle; private final DisplayManager mDisplayManager; private final WindowManager mWindowManager; @@ -92,17 +93,23 @@ final class OverlayDisplayWindow implements DumpUtils.Dump { private float mLiveScale = 1.0f; public OverlayDisplayWindow(Context context, String name, - int width, int height, int densityDpi, int gravity, Listener listener) { + int width, int height, int densityDpi, int gravity, boolean secure, + Listener listener) { mContext = context; mName = name; mWidth = width; mHeight = height; mDensityDpi = densityDpi; mGravity = gravity; + mSecure = secure; mListener = listener; mTitle = context.getResources().getString( com.android.internal.R.string.display_manager_overlay_display_title, mName, mWidth, mHeight, mDensityDpi); + if (secure) { + mTitle += context.getResources().getString( + com.android.internal.R.string.display_manager_overlay_display_secure_suffix); + } mDisplayManager = (DisplayManager)context.getSystemService( Context.DISPLAY_SERVICE); @@ -197,6 +204,9 @@ final class OverlayDisplayWindow implements DumpUtils.Dump { | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE | WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL | WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; + if (mSecure) { + mWindowParams.flags |= WindowManager.LayoutParams.FLAG_SECURE; + } if (DISABLE_MOVE_AND_RESIZE) { mWindowParams.flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE; } diff --git a/services/java/com/android/server/display/PersistentDataStore.java b/services/java/com/android/server/display/PersistentDataStore.java index 105c2534c574..67b36958a35a 100644 --- a/services/java/com/android/server/display/PersistentDataStore.java +++ b/services/java/com/android/server/display/PersistentDataStore.java @@ -105,7 +105,8 @@ final class PersistentDataStore { alias = mRememberedWifiDisplays.get(index).getDeviceAlias(); } if (!Objects.equal(display.getDeviceAlias(), alias)) { - return new WifiDisplay(display.getDeviceAddress(), display.getDeviceName(), alias); + return new WifiDisplay(display.getDeviceAddress(), display.getDeviceName(), + alias, display.isAvailable(), display.canConnect(), display.isRemembered()); } } return display; @@ -259,7 +260,8 @@ final class PersistentDataStore { } mRememberedWifiDisplays.add( - new WifiDisplay(deviceAddress, deviceName, deviceAlias)); + new WifiDisplay(deviceAddress, deviceName, deviceAlias, + false, false, false)); } } } diff --git a/services/java/com/android/server/display/VirtualDisplayAdapter.java b/services/java/com/android/server/display/VirtualDisplayAdapter.java new file mode 100644 index 000000000000..46d473ce6ba3 --- /dev/null +++ b/services/java/com/android/server/display/VirtualDisplayAdapter.java @@ -0,0 +1,177 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.display; + +import android.content.Context; +import android.hardware.display.DisplayManager; +import android.os.Handler; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.RemoteException; +import android.util.ArrayMap; +import android.util.Slog; +import android.view.Display; +import android.view.Surface; +import android.view.SurfaceControl; + +/** + * A display adapter that provides virtual displays on behalf of applications. + * <p> + * Display adapters are guarded by the {@link DisplayManagerService.SyncRoot} lock. + * </p> + */ +final class VirtualDisplayAdapter extends DisplayAdapter { + static final String TAG = "VirtualDisplayAdapter"; + static final boolean DEBUG = false; + + private final ArrayMap<IBinder, VirtualDisplayDevice> mVirtualDisplayDevices = + new ArrayMap<IBinder, VirtualDisplayDevice>(); + + // Called with SyncRoot lock held. + public VirtualDisplayAdapter(DisplayManagerService.SyncRoot syncRoot, + Context context, Handler handler, Listener listener) { + super(syncRoot, context, handler, listener, TAG); + } + + public DisplayDevice createVirtualDisplayLocked(IBinder appToken, + int ownerUid, String ownerPackageName, + String name, int width, int height, int densityDpi, Surface surface, int flags) { + boolean secure = (flags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0; + IBinder displayToken = SurfaceControl.createDisplay(name, secure); + VirtualDisplayDevice device = new VirtualDisplayDevice(displayToken, appToken, + ownerUid, ownerPackageName, name, width, height, densityDpi, surface, flags); + + try { + appToken.linkToDeath(device, 0); + } catch (RemoteException ex) { + device.destroyLocked(); + return null; + } + + mVirtualDisplayDevices.put(appToken, device); + + // Return the display device without actually sending the event indicating + // that it was added. The caller will handle it. + return device; + } + + public DisplayDevice releaseVirtualDisplayLocked(IBinder appToken) { + VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken); + if (device != null) { + device.destroyLocked(); + appToken.unlinkToDeath(device, 0); + } + + // Return the display device that was removed without actually sending the + // event indicating that it was removed. The caller will handle it. + return device; + } + + private void handleBinderDiedLocked(IBinder appToken) { + VirtualDisplayDevice device = mVirtualDisplayDevices.remove(appToken); + if (device != null) { + Slog.i(TAG, "Virtual display device released because application token died: " + + device.mOwnerPackageName); + device.destroyLocked(); + sendDisplayDeviceEventLocked(device, DISPLAY_DEVICE_EVENT_REMOVED); + } + } + + private final class VirtualDisplayDevice extends DisplayDevice + implements DeathRecipient { + private final IBinder mAppToken; + private final int mOwnerUid; + final String mOwnerPackageName; + private final String mName; + private final int mWidth; + private final int mHeight; + private final int mDensityDpi; + private final int mFlags; + + private Surface mSurface; + private DisplayDeviceInfo mInfo; + + public VirtualDisplayDevice(IBinder displayToken, + IBinder appToken, int ownerUid, String ownerPackageName, + String name, int width, int height, int densityDpi, Surface surface, int flags) { + super(VirtualDisplayAdapter.this, displayToken); + mAppToken = appToken; + mOwnerUid = ownerUid; + mOwnerPackageName = ownerPackageName; + mName = name; + mWidth = width; + mHeight = height; + mDensityDpi = densityDpi; + mSurface = surface; + mFlags = flags; + } + + @Override + public void binderDied() { + synchronized (getSyncRoot()) { + if (mSurface != null) { + handleBinderDiedLocked(mAppToken); + } + } + } + + public void destroyLocked() { + if (mSurface != null) { + mSurface.release(); + mSurface = null; + } + SurfaceControl.destroyDisplay(getDisplayTokenLocked()); + } + + @Override + public void performTraversalInTransactionLocked() { + if (mSurface != null) { + setSurfaceInTransactionLocked(mSurface); + } + } + + @Override + public DisplayDeviceInfo getDisplayDeviceInfoLocked() { + if (mInfo == null) { + mInfo = new DisplayDeviceInfo(); + mInfo.name = mName; + mInfo.width = mWidth; + mInfo.height = mHeight; + mInfo.refreshRate = 60; + mInfo.densityDpi = mDensityDpi; + mInfo.xDpi = mDensityDpi; + mInfo.yDpi = mDensityDpi; + mInfo.flags = 0; + if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC) == 0) { + mInfo.flags |= DisplayDeviceInfo.FLAG_PRIVATE | + DisplayDeviceInfo.FLAG_NEVER_BLANK; + } + if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_SECURE) != 0) { + mInfo.flags |= DisplayDeviceInfo.FLAG_SECURE; + } + if ((mFlags & DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION) != 0) { + mInfo.flags |= DisplayDeviceInfo.FLAG_PRESENTATION; + } + mInfo.type = Display.TYPE_VIRTUAL; + mInfo.touch = DisplayDeviceInfo.TOUCH_NONE; + mInfo.ownerUid = mOwnerUid; + mInfo.ownerPackageName = mOwnerPackageName; + } + return mInfo; + } + } +} diff --git a/services/java/com/android/server/display/WifiDisplayAdapter.java b/services/java/com/android/server/display/WifiDisplayAdapter.java index b655b3ad66fc..f7bbdf8246bb 100644 --- a/services/java/com/android/server/display/WifiDisplayAdapter.java +++ b/services/java/com/android/server/display/WifiDisplayAdapter.java @@ -30,6 +30,7 @@ import android.content.IntentFilter; import android.content.res.Resources; import android.hardware.display.DisplayManager; import android.hardware.display.WifiDisplay; +import android.hardware.display.WifiDisplaySessionInfo; import android.hardware.display.WifiDisplayStatus; import android.media.RemoteDisplay; import android.os.Handler; @@ -45,6 +46,8 @@ import android.view.SurfaceControl; import java.io.PrintWriter; import java.util.Arrays; +import java.util.List; +import java.util.ArrayList; import libcore.util.Objects; @@ -88,8 +91,10 @@ final class WifiDisplayAdapter extends DisplayAdapter { private int mScanState; private int mActiveDisplayState; private WifiDisplay mActiveDisplay; + private WifiDisplay[] mDisplays = WifiDisplay.EMPTY_ARRAY; private WifiDisplay[] mAvailableDisplays = WifiDisplay.EMPTY_ARRAY; private WifiDisplay[] mRememberedDisplays = WifiDisplay.EMPTY_ARRAY; + private WifiDisplaySessionInfo mSessionInfo; private boolean mPendingStatusChangeBroadcast; private boolean mPendingNotificationUpdate; @@ -116,6 +121,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { pw.println("mScanState=" + mScanState); pw.println("mActiveDisplayState=" + mActiveDisplayState); pw.println("mActiveDisplay=" + mActiveDisplay); + pw.println("mDisplays=" + Arrays.toString(mDisplays)); pw.println("mAvailableDisplays=" + Arrays.toString(mAvailableDisplays)); pw.println("mRememberedDisplays=" + Arrays.toString(mRememberedDisplays)); pw.println("mPendingStatusChangeBroadcast=" + mPendingStatusChangeBroadcast); @@ -200,6 +206,36 @@ final class WifiDisplayAdapter extends DisplayAdapter { return false; } + public void requestPauseLocked() { + if (DEBUG) { + Slog.d(TAG, "requestPauseLocked"); + } + + getHandler().post(new Runnable() { + @Override + public void run() { + if (mDisplayController != null) { + mDisplayController.requestPause(); + } + } + }); + } + + public void requestResumeLocked() { + if (DEBUG) { + Slog.d(TAG, "requestResumeLocked"); + } + + getHandler().post(new Runnable() { + @Override + public void run() { + if (mDisplayController != null) { + mDisplayController.requestResume(); + } + } + }); + } + public void requestDisconnectLocked() { if (DEBUG) { Slog.d(TAG, "requestDisconnectedLocked"); @@ -229,7 +265,8 @@ final class WifiDisplayAdapter extends DisplayAdapter { WifiDisplay display = mPersistentDataStore.getRememberedWifiDisplay(address); if (display != null && !Objects.equal(display.getDeviceAlias(), alias)) { - display = new WifiDisplay(address, display.getDeviceName(), alias); + display = new WifiDisplay(address, display.getDeviceName(), alias, + false, false, false); if (mPersistentDataStore.rememberWifiDisplay(display)) { mPersistentDataStore.saveIfNeeded(); updateRememberedDisplaysLocked(); @@ -262,7 +299,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { if (mCurrentStatus == null) { mCurrentStatus = new WifiDisplayStatus( mFeatureState, mScanState, mActiveDisplayState, - mActiveDisplay, mAvailableDisplays, mRememberedDisplays); + mActiveDisplay, mDisplays, mSessionInfo); } if (DEBUG) { @@ -271,10 +308,36 @@ final class WifiDisplayAdapter extends DisplayAdapter { return mCurrentStatus; } + private void updateDisplaysLocked() { + List<WifiDisplay> displays = new ArrayList<WifiDisplay>( + mAvailableDisplays.length + mRememberedDisplays.length); + boolean[] remembered = new boolean[mAvailableDisplays.length]; + for (WifiDisplay d : mRememberedDisplays) { + boolean available = false; + for (int i = 0; i < mAvailableDisplays.length; i++) { + if (d.equals(mAvailableDisplays[i])) { + remembered[i] = available = true; + break; + } + } + if (!available) { + displays.add(new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(), + d.getDeviceAlias(), false, false, true)); + } + } + for (int i = 0; i < mAvailableDisplays.length; i++) { + WifiDisplay d = mAvailableDisplays[i]; + displays.add(new WifiDisplay(d.getDeviceAddress(), d.getDeviceName(), + d.getDeviceAlias(), true, d.canConnect(), remembered[i])); + } + mDisplays = displays.toArray(WifiDisplay.EMPTY_ARRAY); + } + private void updateRememberedDisplaysLocked() { mRememberedDisplays = mPersistentDataStore.getRememberedWifiDisplays(); mActiveDisplay = mPersistentDataStore.applyWifiDisplayAlias(mActiveDisplay); mAvailableDisplays = mPersistentDataStore.applyWifiDisplayAliases(mAvailableDisplays); + updateDisplaysLocked(); } private void fixRememberedDisplayNamesFromAvailableDisplaysLocked() { @@ -321,7 +384,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { } boolean secure = (flags & RemoteDisplay.DISPLAY_FLAG_SECURE) != 0; - int deviceFlags = 0; + int deviceFlags = DisplayDeviceInfo.FLAG_PRESENTATION; if (secure) { deviceFlags |= DisplayDeviceInfo.FLAG_SECURE; if (mSupportsProtectedBuffers) { @@ -343,7 +406,7 @@ final class WifiDisplayAdapter extends DisplayAdapter { private void removeDisplayDeviceLocked() { if (mDisplayDevice != null) { - mDisplayDevice.clearSurfaceLocked(); + mDisplayDevice.destroyLocked(); sendDisplayDeviceEventLocked(mDisplayDevice, DISPLAY_DEVICE_EVENT_REMOVED); mDisplayDevice = null; @@ -487,11 +550,18 @@ final class WifiDisplayAdapter extends DisplayAdapter { availableDisplays = mPersistentDataStore.applyWifiDisplayAliases( availableDisplays); - if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING - || !Arrays.equals(mAvailableDisplays, availableDisplays)) { + // check if any of the available displays changed canConnect status + boolean changed = !Arrays.equals(mAvailableDisplays, availableDisplays); + for (int i = 0; !changed && i<availableDisplays.length; i++) { + changed = availableDisplays[i].canConnect() + != mAvailableDisplays[i].canConnect(); + } + + if (mScanState != WifiDisplayStatus.SCAN_STATE_NOT_SCANNING || changed) { mScanState = WifiDisplayStatus.SCAN_STATE_NOT_SCANNING; mAvailableDisplays = availableDisplays; fixRememberedDisplayNamesFromAvailableDisplaysLocked(); + updateDisplaysLocked(); scheduleStatusChangedBroadcastLocked(); } } @@ -542,6 +612,14 @@ final class WifiDisplayAdapter extends DisplayAdapter { } @Override + public void onDisplaySessionInfo(WifiDisplaySessionInfo sessionInfo) { + synchronized (getSyncRoot()) { + mSessionInfo = sessionInfo; + scheduleStatusChangedBroadcastLocked(); + } + } + + @Override public void onDisplayChanged(WifiDisplay display) { synchronized (getSyncRoot()) { display = mPersistentDataStore.applyWifiDisplayAlias(display); @@ -595,9 +673,12 @@ final class WifiDisplayAdapter extends DisplayAdapter { mSurface = surface; } - public void clearSurfaceLocked() { - mSurface = null; - sendTraversalRequestLocked(); + public void destroyLocked() { + if (mSurface != null) { + mSurface.release(); + mSurface = null; + } + SurfaceControl.destroyDisplay(getDisplayTokenLocked()); } public void setNameLocked(String name) { @@ -607,7 +688,9 @@ final class WifiDisplayAdapter extends DisplayAdapter { @Override public void performTraversalInTransactionLocked() { - setSurfaceInTransactionLocked(mSurface); + if (mSurface != null) { + setSurfaceInTransactionLocked(mSurface); + } } @Override diff --git a/services/java/com/android/server/display/WifiDisplayController.java b/services/java/com/android/server/display/WifiDisplayController.java index 8c8b360c480e..9a4cfb77775a 100644 --- a/services/java/com/android/server/display/WifiDisplayController.java +++ b/services/java/com/android/server/display/WifiDisplayController.java @@ -25,6 +25,7 @@ import android.content.Intent; import android.content.IntentFilter; import android.database.ContentObserver; import android.hardware.display.WifiDisplay; +import android.hardware.display.WifiDisplaySessionInfo; import android.hardware.display.WifiDisplayStatus; import android.media.AudioManager; import android.media.RemoteDisplay; @@ -76,6 +77,7 @@ final class WifiDisplayController implements DumpUtils.Dump { private static final int MAX_THROUGHPUT = 50; private static final int CONNECTION_TIMEOUT_SECONDS = 60; private static final int RTSP_TIMEOUT_SECONDS = 15; + private static final int RTSP_TIMEOUT_SECONDS_CERT_MODE = 120; private static final int DISCOVER_PEERS_MAX_RETRIES = 10; private static final int DISCOVER_PEERS_RETRY_DELAY_MILLIS = 500; @@ -83,11 +85,6 @@ final class WifiDisplayController implements DumpUtils.Dump { private static final int CONNECT_MAX_RETRIES = 3; private static final int CONNECT_RETRY_DELAY_MILLIS = 500; - // A unique token to identify the remote submix that is managed by Wifi display. - // It must match what the media server uses when it starts recording the submix - // for transmission. We use 0 although the actual value is currently ignored. - private static final int REMOTE_SUBMIX_ADDRESS = 0; - private final Context mContext; private final Handler mHandler; private final Listener mListener; @@ -95,8 +92,6 @@ final class WifiDisplayController implements DumpUtils.Dump { private final WifiP2pManager mWifiP2pManager; private final Channel mWifiP2pChannel; - private final AudioManager mAudioManager; - private boolean mWifiP2pEnabled; private boolean mWfdEnabled; private boolean mWfdEnabling; @@ -146,9 +141,6 @@ final class WifiDisplayController implements DumpUtils.Dump { // True if RTSP has connected. private boolean mRemoteDisplayConnected; - // True if the remote submix is enabled. - private boolean mRemoteSubmixOn; - // The information we have most recently told WifiDisplayAdapter about. private WifiDisplay mAdvertisedDisplay; private Surface mAdvertisedDisplaySurface; @@ -156,6 +148,12 @@ final class WifiDisplayController implements DumpUtils.Dump { private int mAdvertisedDisplayHeight; private int mAdvertisedDisplayFlags; + // Certification + private boolean mWifiDisplayCertMode; + private int mWifiDisplayWpsConfig = WpsInfo.INVALID; + + private WifiP2pDevice mThisDevice; + public WifiDisplayController(Context context, Handler handler, Listener listener) { mContext = context; mHandler = handler; @@ -164,12 +162,11 @@ final class WifiDisplayController implements DumpUtils.Dump { mWifiP2pManager = (WifiP2pManager)context.getSystemService(Context.WIFI_P2P_SERVICE); mWifiP2pChannel = mWifiP2pManager.initialize(context, handler.getLooper(), null); - mAudioManager = (AudioManager)context.getSystemService(Context.AUDIO_SERVICE); - IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION); intentFilter.addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION); + intentFilter.addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION); context.registerReceiver(mWifiP2pReceiver, intentFilter, null, mHandler); ContentObserver settingsObserver = new ContentObserver(mHandler) { @@ -182,6 +179,10 @@ final class WifiDisplayController implements DumpUtils.Dump { final ContentResolver resolver = mContext.getContentResolver(); resolver.registerContentObserver(Settings.Global.getUriFor( Settings.Global.WIFI_DISPLAY_ON), false, settingsObserver); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON), false, settingsObserver); + resolver.registerContentObserver(Settings.Global.getUriFor( + Settings.Global.WIFI_DISPLAY_WPS_CONFIG), false, settingsObserver); updateSettings(); } @@ -189,6 +190,14 @@ final class WifiDisplayController implements DumpUtils.Dump { final ContentResolver resolver = mContext.getContentResolver(); mWifiDisplayOnSetting = Settings.Global.getInt(resolver, Settings.Global.WIFI_DISPLAY_ON, 0) != 0; + mWifiDisplayCertMode = Settings.Global.getInt(resolver, + Settings.Global.WIFI_DISPLAY_CERTIFICATION_ON, 0) != 0; + + mWifiDisplayWpsConfig = WpsInfo.INVALID; + if (mWifiDisplayCertMode) { + mWifiDisplayWpsConfig = Settings.Global.getInt(resolver, + Settings.Global.WIFI_DISPLAY_WPS_CONFIG, WpsInfo.INVALID); + } updateWfdEnableState(); } @@ -211,7 +220,6 @@ final class WifiDisplayController implements DumpUtils.Dump { pw.println("mRemoteDisplay=" + mRemoteDisplay); pw.println("mRemoteDisplayInterface=" + mRemoteDisplayInterface); pw.println("mRemoteDisplayConnected=" + mRemoteDisplayConnected); - pw.println("mRemoteSubmixOn=" + mRemoteSubmixOn); pw.println("mAdvertisedDisplay=" + mAdvertisedDisplay); pw.println("mAdvertisedDisplaySurface=" + mAdvertisedDisplaySurface); pw.println("mAdvertisedDisplayWidth=" + mAdvertisedDisplayWidth); @@ -236,6 +244,18 @@ final class WifiDisplayController implements DumpUtils.Dump { } } + public void requestPause() { + if (mRemoteDisplay != null) { + mRemoteDisplay.pause(); + } + } + + public void requestResume() { + if (mRemoteDisplay != null) { + mRemoteDisplay.resume(); + } + } + public void requestDisconnect() { disconnect(); } @@ -276,6 +296,25 @@ final class WifiDisplayController implements DumpUtils.Dump { } } else { // WFD should be disabled. + if (mWfdEnabled || mWfdEnabling) { + WifiP2pWfdInfo wfdInfo = new WifiP2pWfdInfo(); + wfdInfo.setWfdEnabled(false); + mWifiP2pManager.setWFDInfo(mWifiP2pChannel, wfdInfo, new ActionListener() { + @Override + public void onSuccess() { + if (DEBUG) { + Slog.d(TAG, "Successfully set WFD info."); + } + } + + @Override + public void onFailure(int reason) { + if (DEBUG) { + Slog.d(TAG, "Failed to set WFD info with reason " + reason + "."); + } + } + }); + } mWfdEnabling = false; mWfdEnabled = false; reportFeatureState(); @@ -482,7 +521,6 @@ final class WifiDisplayController implements DumpUtils.Dump { mHandler.removeCallbacks(mRtspTimeout); mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_DISABLED); - setRemoteSubmixOn(false); unadvertiseDisplay(); // continue to next step @@ -496,6 +534,7 @@ final class WifiDisplayController implements DumpUtils.Dump { Slog.i(TAG, "Disconnecting from Wifi display: " + mConnectedDevice.deviceName); mDisconnectingDevice = mConnectedDevice; mConnectedDevice = null; + mConnectedDeviceGroupInfo = null; unadvertiseDisplay(); @@ -562,8 +601,12 @@ final class WifiDisplayController implements DumpUtils.Dump { return; // wait for asynchronous callback } - // Step 4. If we wanted to disconnect, then mission accomplished. + // Step 4. If we wanted to disconnect, or we're updating after starting an + // autonomous GO, then mission accomplished. if (mDesiredDevice == null) { + if (mWifiDisplayCertMode) { + mListener.onDisplaySessionInfo(getSessionInfo(mConnectedDeviceGroupInfo, 0)); + } unadvertiseDisplay(); return; // done } @@ -575,7 +618,9 @@ final class WifiDisplayController implements DumpUtils.Dump { mConnectingDevice = mDesiredDevice; WifiP2pConfig config = new WifiP2pConfig(); WpsInfo wps = new WpsInfo(); - if (mConnectingDevice.wpsPbcSupported()) { + if (mWifiDisplayWpsConfig != WpsInfo.INVALID) { + wps.setup = mWifiDisplayWpsConfig; + } else if (mConnectingDevice.wpsPbcSupported()) { wps.setup = WpsInfo.PBC; } else if (mConnectingDevice.wpsDisplaySupported()) { // We do keypad if peer does display @@ -626,7 +671,6 @@ final class WifiDisplayController implements DumpUtils.Dump { return; // done } - setRemoteSubmixOn(true); mWifiP2pManager.setMiracastMode(WifiP2pManager.MIRACAST_SOURCE); final WifiP2pDevice oldDevice = mConnectedDevice; @@ -640,13 +684,18 @@ final class WifiDisplayController implements DumpUtils.Dump { mRemoteDisplay = RemoteDisplay.listen(iface, new RemoteDisplay.Listener() { @Override public void onDisplayConnected(Surface surface, - int width, int height, int flags) { + int width, int height, int flags, int session) { if (mConnectedDevice == oldDevice && !mRemoteDisplayConnected) { Slog.i(TAG, "Opened RTSP connection with Wifi display: " + mConnectedDevice.deviceName); mRemoteDisplayConnected = true; mHandler.removeCallbacks(mRtspTimeout); + if (mWifiDisplayCertMode) { + mListener.onDisplaySessionInfo( + getSessionInfo(mConnectedDeviceGroupInfo, session)); + } + final WifiDisplay display = createWifiDisplay(mConnectedDevice); advertiseDisplay(display, surface, width, height, flags); } @@ -673,15 +722,29 @@ final class WifiDisplayController implements DumpUtils.Dump { } }, mHandler); - mHandler.postDelayed(mRtspTimeout, RTSP_TIMEOUT_SECONDS * 1000); + // Use extended timeout value for certification, as some tests require user inputs + int rtspTimeout = mWifiDisplayCertMode ? + RTSP_TIMEOUT_SECONDS_CERT_MODE : RTSP_TIMEOUT_SECONDS; + + mHandler.postDelayed(mRtspTimeout, rtspTimeout * 1000); } } - private void setRemoteSubmixOn(boolean on) { - if (mRemoteSubmixOn != on) { - mRemoteSubmixOn = on; - mAudioManager.setRemoteSubmixOn(on, REMOTE_SUBMIX_ADDRESS); + private WifiDisplaySessionInfo getSessionInfo(WifiP2pGroup info, int session) { + if (info == null) { + return null; + } + Inet4Address addr = getInterfaceAddress(info); + WifiDisplaySessionInfo sessionInfo = new WifiDisplaySessionInfo( + !info.getOwner().deviceAddress.equals(mThisDevice.deviceAddress), + session, + info.getOwner().deviceAddress + " " + info.getNetworkName(), + info.getPassphrase(), + (addr != null) ? addr.getHostAddress() : ""); + if (DEBUG) { + Slog.d(TAG, sessionInfo.toString()); } + return sessionInfo; } private void handleStateChanged(boolean enabled) { @@ -698,7 +761,7 @@ final class WifiDisplayController implements DumpUtils.Dump { private void handleConnectionChanged(NetworkInfo networkInfo) { mNetworkInfo = networkInfo; if (mWfdEnabled && networkInfo.isConnected()) { - if (mDesiredDevice != null) { + if (mDesiredDevice != null || mWifiDisplayCertMode) { mWifiP2pManager.requestGroupInfo(mWifiP2pChannel, new GroupInfoListener() { @Override public void onGroupInfoAvailable(WifiP2pGroup info) { @@ -720,6 +783,25 @@ final class WifiDisplayController implements DumpUtils.Dump { return; } + if (mWifiDisplayCertMode) { + boolean owner = info.getOwner().deviceAddress + .equals(mThisDevice.deviceAddress); + if (owner && info.getClientList().isEmpty()) { + // this is the case when we started Autonomous GO, + // and no client has connected, save group info + // and updateConnection() + mConnectingDevice = mDesiredDevice = null; + mConnectedDeviceGroupInfo = info; + updateConnection(); + } else if (mConnectingDevice == null && mDesiredDevice == null) { + // this is the case when we received an incoming connection + // from the sink, update both mConnectingDevice and mDesiredDevice + // then proceed to updateConnection() below + mConnectingDevice = mDesiredDevice = owner ? + info.getClientList().iterator().next() : info.getOwner(); + } + } + if (mConnectingDevice != null && mConnectingDevice == mDesiredDevice) { Slog.i(TAG, "Connected to Wifi display: " + mConnectingDevice.deviceName); @@ -734,6 +816,7 @@ final class WifiDisplayController implements DumpUtils.Dump { }); } } else { + mConnectedDeviceGroupInfo = null; disconnect(); // After disconnection for a group, for some reason we have a tendency @@ -897,7 +980,8 @@ final class WifiDisplayController implements DumpUtils.Dump { } private static WifiDisplay createWifiDisplay(WifiP2pDevice device) { - return new WifiDisplay(device.deviceAddress, device.deviceName, null); + return new WifiDisplay(device.deviceAddress, device.deviceName, null, + true, device.wfdInfo.isSessionAvailable(), false); } private final BroadcastReceiver mWifiP2pReceiver = new BroadcastReceiver() { @@ -931,6 +1015,13 @@ final class WifiDisplayController implements DumpUtils.Dump { } handleConnectionChanged(networkInfo); + } else if (action.equals(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)) { + mThisDevice = (WifiP2pDevice) intent.getParcelableExtra( + WifiP2pManager.EXTRA_WIFI_P2P_DEVICE); + if (DEBUG) { + Slog.d(TAG, "Received WIFI_P2P_THIS_DEVICE_CHANGED_ACTION: mThisDevice= " + + mThisDevice); + } } } }; @@ -949,6 +1040,7 @@ final class WifiDisplayController implements DumpUtils.Dump { void onDisplayChanged(WifiDisplay display); void onDisplayConnected(WifiDisplay display, Surface surface, int width, int height, int flags); + void onDisplaySessionInfo(WifiDisplaySessionInfo sessionInfo); void onDisplayDisconnected(); } } diff --git a/services/java/com/android/server/dreams/DreamManagerService.java b/services/java/com/android/server/dreams/DreamManagerService.java index c9e0da5b8f54..b6e778170d17 100644 --- a/services/java/com/android/server/dreams/DreamManagerService.java +++ b/services/java/com/android/server/dreams/DreamManagerService.java @@ -73,7 +73,7 @@ public final class DreamManagerService extends IDreamManager.Stub { mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE); } - public void systemReady() { + public void systemRunning() { mContext.registerReceiver(new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { @@ -86,7 +86,13 @@ public final class DreamManagerService extends IDreamManager.Stub { @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { - mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, TAG); + if (mContext.checkCallingOrSelfPermission("android.permission.DUMP") + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump DreamManager from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } pw.println("DREAM MANAGER (dumpsys dreams)"); pw.println(); diff --git a/services/java/com/android/server/firewall/AndFilter.java b/services/java/com/android/server/firewall/AndFilter.java index e4276d085196..13551de8a02f 100644 --- a/services/java/com/android/server/firewall/AndFilter.java +++ b/services/java/com/android/server/firewall/AndFilter.java @@ -16,8 +16,8 @@ package com.android.server.firewall; +import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -25,11 +25,11 @@ import java.io.IOException; class AndFilter extends FilterList { @Override - public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, - int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { for (int i=0; i<children.size(); i++) { - if (!children.get(i).matches(ifw, intent, callerApp, callerUid, callerPid, resolvedType, - resolvedApp)) { + if (!children.get(i).matches(ifw, resolvedComponent, intent, callerUid, callerPid, + resolvedType, receivingUid)) { return false; } } diff --git a/services/java/com/android/server/firewall/CategoryFilter.java b/services/java/com/android/server/firewall/CategoryFilter.java index 4938cb89bba9..246c09630c67 100644 --- a/services/java/com/android/server/firewall/CategoryFilter.java +++ b/services/java/com/android/server/firewall/CategoryFilter.java @@ -16,8 +16,8 @@ package com.android.server.firewall; +import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -34,8 +34,8 @@ class CategoryFilter implements Filter { } @Override - public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, - int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { Set<String> categories = intent.getCategories(); if (categories == null) { return false; diff --git a/services/java/com/android/server/firewall/Filter.java b/services/java/com/android/server/firewall/Filter.java index 0e783e80316f..0a124f768f6c 100644 --- a/services/java/com/android/server/firewall/Filter.java +++ b/services/java/com/android/server/firewall/Filter.java @@ -16,24 +16,21 @@ package com.android.server.firewall; +import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; interface Filter { /** * Does the given intent + context info match this filter? * * @param ifw The IntentFirewall instance + * @param resolvedComponent The actual component that the intent was resolved to * @param intent The intent being started/bound/broadcast - * @param callerApp An ApplicationInfo of an application in the caller's process. This may not - * be the specific app that is actually sending the intent. This also may be - * null, if the caller is the system process, or an unrecognized process (e.g. - * am start) - * @param callerUid - * @param callerPid + * @param callerUid The uid of the caller + * @param callerPid The pid of the caller * @param resolvedType The resolved mime type of the intent - * @param resolvedApp The application that contains the resolved component that the intent is + * @param receivingUid The uid of the component receiving the intent */ - boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, - int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp); + boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid); } diff --git a/services/java/com/android/server/firewall/IntentFirewall.java b/services/java/com/android/server/firewall/IntentFirewall.java index 4496aae7b6e4..aaa0b585eddb 100644 --- a/services/java/com/android/server/firewall/IntentFirewall.java +++ b/services/java/com/android/server/firewall/IntentFirewall.java @@ -20,7 +20,6 @@ import android.app.AppGlobals; import android.content.ComponentName; import android.content.Intent; import android.content.IntentFilter; -import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.IPackageManager; import android.content.pm.PackageManager; @@ -29,8 +28,10 @@ import android.os.FileObserver; import android.os.Handler; import android.os.Message; import android.os.RemoteException; +import android.util.ArrayMap; import android.util.Slog; import android.util.Xml; +import com.android.internal.util.ArrayUtils; import com.android.internal.util.XmlUtils; import com.android.server.EventLogTags; import com.android.server.IntentResolver; @@ -42,15 +43,15 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.List; public class IntentFirewall { - private static final String TAG = "IntentFirewall"; + static final String TAG = "IntentFirewall"; - // e.g. /data/system/ifw/ifw.xml or /data/secure/system/ifw/ifw.xml - private static final File RULES_FILE = - new File(Environment.getSystemSecureDirectory(), "ifw/ifw.xml"); + // e.g. /data/system/ifw or /data/secure/system/ifw + private static final File RULES_DIR = new File(Environment.getSystemSecureDirectory(), "ifw"); private static final int LOG_PACKAGES_MAX_LENGTH = 150; private static final int LOG_PACKAGES_SUFFICIENT_LENGTH = 125; @@ -87,6 +88,7 @@ public class IntentFirewall { StringFilter.DATA, StringFilter.HOST, StringFilter.MIME_TYPE, + StringFilter.SCHEME, StringFilter.PATH, StringFilter.SSP, @@ -106,12 +108,12 @@ public class IntentFirewall { public IntentFirewall(AMSInterface ams) { mAms = ams; - File rulesFile = getRulesFile(); - rulesFile.getParentFile().mkdirs(); + File rulesDir = getRulesDir(); + rulesDir.mkdirs(); - readRules(rulesFile); + readRulesDir(rulesDir); - mObserver = new RuleObserver(rulesFile); + mObserver = new RuleObserver(rulesDir); mObserver.startWatching(); } @@ -119,16 +121,45 @@ public class IntentFirewall { * This is called from ActivityManager to check if a start activity intent should be allowed. * It is assumed the caller is already holding the global ActivityManagerService lock. */ - public boolean checkStartActivity(Intent intent, ApplicationInfo callerApp, int callerUid, - int callerPid, String resolvedType, ActivityInfo resolvedActivity) { - List<Rule> matchingRules = mActivityResolver.queryIntent(intent, resolvedType, false, 0); + public boolean checkStartActivity(Intent intent, int callerUid, int callerPid, + String resolvedType, ApplicationInfo resolvedApp) { + return checkIntent(mActivityResolver, intent.getComponent(), TYPE_ACTIVITY, intent, + callerUid, callerPid, resolvedType, resolvedApp.uid); + } + + public boolean checkService(ComponentName resolvedService, Intent intent, int callerUid, + int callerPid, String resolvedType, ApplicationInfo resolvedApp) { + return checkIntent(mServiceResolver, resolvedService, TYPE_SERVICE, intent, callerUid, + callerPid, resolvedType, resolvedApp.uid); + } + + public boolean checkBroadcast(Intent intent, int callerUid, int callerPid, + String resolvedType, int receivingUid) { + return checkIntent(mBroadcastResolver, intent.getComponent(), TYPE_BROADCAST, intent, + callerUid, callerPid, resolvedType, receivingUid); + } + + public boolean checkIntent(FirewallIntentResolver resolver, ComponentName resolvedComponent, + int intentType, Intent intent, int callerUid, int callerPid, String resolvedType, + int receivingUid) { boolean log = false; boolean block = false; - for (int i=0; i< matchingRules.size(); i++) { - Rule rule = matchingRules.get(i); - if (rule.matches(this, intent, callerApp, callerUid, callerPid, resolvedType, - resolvedActivity.applicationInfo)) { + // For the first pass, find all the rules that have at least one intent-filter or + // component-filter that matches this intent + List<Rule> candidateRules; + candidateRules = resolver.queryIntent(intent, resolvedType, false, 0); + if (candidateRules == null) { + candidateRules = new ArrayList<Rule>(); + } + resolver.queryByComponent(resolvedComponent, candidateRules); + + // For the second pass, try to match the potentially more specific conditions in each + // rule against the intent + for (int i=0; i<candidateRules.size(); i++) { + Rule rule = candidateRules.get(i); + if (rule.matches(this, resolvedComponent, intent, callerUid, callerPid, resolvedType, + receivingUid)) { block |= rule.getBlock(); log |= rule.getLog(); @@ -141,7 +172,7 @@ public class IntentFirewall { } if (log) { - logIntent(TYPE_ACTIVITY, intent, callerUid, resolvedType); + logIntent(intentType, intent, callerUid, resolvedType); } return !block; @@ -216,22 +247,58 @@ public class IntentFirewall { return null; } - public static File getRulesFile() { - return RULES_FILE; + public static File getRulesDir() { + return RULES_DIR; } /** - * Reads rules from the given file and replaces our set of rules with the newly read rules + * Reads rules from all xml files (*.xml) in the given directory, and replaces our set of rules + * with the newly read rules. + * + * We only check for files ending in ".xml", to allow for temporary files that are atomically + * renamed to .xml * * All calls to this method from the file observer come through a handler and are inherently * serialized */ - private void readRules(File rulesFile) { + private void readRulesDir(File rulesDir) { FirewallIntentResolver[] resolvers = new FirewallIntentResolver[3]; for (int i=0; i<resolvers.length; i++) { resolvers[i] = new FirewallIntentResolver(); } + File[] files = rulesDir.listFiles(); + for (int i=0; i<files.length; i++) { + File file = files[i]; + + if (file.getName().endsWith(".xml")) { + readRules(file, resolvers); + } + } + + Slog.i(TAG, "Read new rules (A:" + resolvers[TYPE_ACTIVITY].filterSet().size() + + " B:" + resolvers[TYPE_BROADCAST].filterSet().size() + + " S:" + resolvers[TYPE_SERVICE].filterSet().size() + ")"); + + synchronized (mAms.getAMSLock()) { + mActivityResolver = resolvers[TYPE_ACTIVITY]; + mBroadcastResolver = resolvers[TYPE_BROADCAST]; + mServiceResolver = resolvers[TYPE_SERVICE]; + } + } + + /** + * Reads rules from the given file and add them to the given resolvers + */ + private void readRules(File rulesFile, FirewallIntentResolver[] resolvers) { + // some temporary lists to hold the rules while we parse the xml file, so that we can + // add the rules all at once, after we know there weren't any major structural problems + // with the xml file + List<List<Rule>> rulesByType = new ArrayList<List<Rule>>(3); + for (int i=0; i<3; i++) { + rulesByType.add(new ArrayList<Rule>()); + } + FileInputStream fis; try { fis = new FileInputStream(rulesFile); @@ -247,8 +314,6 @@ public class IntentFirewall { XmlUtils.beginDocument(parser, TAG_RULES); - int[] numRules = new int[3]; - int outerDepth = parser.getDepth(); while (XmlUtils.nextElementWithin(parser, outerDepth)) { int ruleType = -1; @@ -265,41 +330,28 @@ public class IntentFirewall { if (ruleType != -1) { Rule rule = new Rule(); - FirewallIntentResolver resolver = resolvers[ruleType]; + List<Rule> rules = rulesByType.get(ruleType); // if we get an error while parsing a particular rule, we'll just ignore // that rule and continue on with the next rule try { rule.readFromXml(parser); } catch (XmlPullParserException ex) { - Slog.e(TAG, "Error reading intent firewall rule", ex); + Slog.e(TAG, "Error reading an intent firewall rule from " + rulesFile, ex); continue; } - numRules[ruleType]++; - - for (int i=0; i<rule.getIntentFilterCount(); i++) { - resolver.addFilter(rule.getIntentFilter(i)); - } + rules.add(rule); } } - - Slog.i(TAG, "Read new rules (A:" + numRules[TYPE_ACTIVITY] + - " B:" + numRules[TYPE_BROADCAST] + " S:" + numRules[TYPE_SERVICE] + ")"); - - synchronized (mAms.getAMSLock()) { - mActivityResolver = resolvers[TYPE_ACTIVITY]; - mBroadcastResolver = resolvers[TYPE_BROADCAST]; - mServiceResolver = resolvers[TYPE_SERVICE]; - } } catch (XmlPullParserException ex) { // if there was an error outside of a specific rule, then there are probably // structural problems with the xml file, and we should completely ignore it - Slog.e(TAG, "Error reading intent firewall rules", ex); - clearRules(); + Slog.e(TAG, "Error reading intent firewall rules from " + rulesFile, ex); + return; } catch (IOException ex) { - Slog.e(TAG, "Error reading intent firewall rules", ex); - clearRules(); + Slog.e(TAG, "Error reading intent firewall rules from " + rulesFile, ex); + return; } finally { try { fis.close(); @@ -307,21 +359,20 @@ public class IntentFirewall { Slog.e(TAG, "Error while closing " + rulesFile, ex); } } - } - /** - * Clears out all of our rules - * - * All calls to this method from the file observer come through a handler and are inherently - * serialized - */ - private void clearRules() { - Slog.i(TAG, "Clearing all rules"); + for (int ruleType=0; ruleType<rulesByType.size(); ruleType++) { + List<Rule> rules = rulesByType.get(ruleType); + FirewallIntentResolver resolver = resolvers[ruleType]; - synchronized (mAms.getAMSLock()) { - mActivityResolver = new FirewallIntentResolver(); - mBroadcastResolver = new FirewallIntentResolver(); - mServiceResolver = new FirewallIntentResolver(); + for (int ruleIndex=0; ruleIndex<rules.size(); ruleIndex++) { + Rule rule = rules.get(ruleIndex); + for (int i=0; i<rule.getIntentFilterCount(); i++) { + resolver.addFilter(rule.getIntentFilter(i)); + } + for (int i=0; i<rule.getComponentFilterCount(); i++) { + resolver.addComponentFilter(rule.getComponentFilter(i), rule); + } + } } } @@ -336,14 +387,35 @@ public class IntentFirewall { return factory.newFilter(parser); } + /** + * Represents a single activity/service/broadcast rule within one of the xml files. + * + * Rules are matched against an incoming intent in two phases. The goal of the first phase + * is to select a subset of rules that might match a given intent. + * + * For the first phase, we use a combination of intent filters (via an IntentResolver) + * and component filters to select which rules to check. If a rule has multiple intent or + * component filters, only a single filter must match for the rule to be passed on to the + * second phase. + * + * In the second phase, we check the specific conditions in each rule against the values in the + * intent. All top level conditions (but not filters) in the rule must match for the rule as a + * whole to match. + * + * If the rule matches, then we block or log the intent, as specified by the rule. If multiple + * rules match, we combine the block/log flags from any matching rule. + */ private static class Rule extends AndFilter { private static final String TAG_INTENT_FILTER = "intent-filter"; + private static final String TAG_COMPONENT_FILTER = "component-filter"; + private static final String ATTR_NAME = "name"; private static final String ATTR_BLOCK = "block"; private static final String ATTR_LOG = "log"; private final ArrayList<FirewallIntentFilter> mIntentFilters = new ArrayList<FirewallIntentFilter>(1); + private final ArrayList<ComponentName> mComponentFilters = new ArrayList<ComponentName>(0); private boolean block; private boolean log; @@ -358,10 +430,25 @@ public class IntentFirewall { @Override protected void readChild(XmlPullParser parser) throws IOException, XmlPullParserException { - if (parser.getName().equals(TAG_INTENT_FILTER)) { + String currentTag = parser.getName(); + + if (currentTag.equals(TAG_INTENT_FILTER)) { FirewallIntentFilter intentFilter = new FirewallIntentFilter(this); intentFilter.readFromXml(parser); mIntentFilters.add(intentFilter); + } else if (currentTag.equals(TAG_COMPONENT_FILTER)) { + String componentStr = parser.getAttributeValue(null, ATTR_NAME); + if (componentStr == null) { + throw new XmlPullParserException("Component name must be specified.", + parser, null); + } + + ComponentName componentName = ComponentName.unflattenFromString(componentStr); + if (componentName == null) { + throw new XmlPullParserException("Invalid component name: " + componentStr); + } + + mComponentFilters.add(componentName); } else { super.readChild(parser); } @@ -375,6 +462,13 @@ public class IntentFirewall { return mIntentFilters.get(index); } + public int getComponentFilterCount() { + return mComponentFilters.size(); + } + + public ComponentName getComponentFilter(int index) { + return mComponentFilters.get(index); + } public boolean getBlock() { return block; } @@ -419,56 +513,50 @@ public class IntentFirewall { // there's no need to sort the results return; } - } - private static final int READ_RULES = 0; - private static final int CLEAR_RULES = 1; + public void queryByComponent(ComponentName componentName, List<Rule> candidateRules) { + Rule[] rules = mRulesByComponent.get(componentName); + if (rules != null) { + candidateRules.addAll(Arrays.asList(rules)); + } + } + + public void addComponentFilter(ComponentName componentName, Rule rule) { + Rule[] rules = mRulesByComponent.get(componentName); + rules = ArrayUtils.appendElement(Rule.class, rules, rule); + mRulesByComponent.put(componentName, rules); + } + + private final ArrayMap<ComponentName, Rule[]> mRulesByComponent = + new ArrayMap<ComponentName, Rule[]>(0); + } final Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { - switch (msg.what) { - case READ_RULES: - readRules(getRulesFile()); - break; - case CLEAR_RULES: - clearRules(); - break; - } + readRulesDir(getRulesDir()); } }; /** - * Monitors for the creation/deletion/modification of the rule file + * Monitors for the creation/deletion/modification of any .xml files in the rule directory */ private class RuleObserver extends FileObserver { - // The file name we're monitoring, with no path component - private final String mMonitoredFile; - - private static final int CREATED_FLAGS = FileObserver.CREATE|FileObserver.MOVED_TO| - FileObserver.CLOSE_WRITE; - private static final int DELETED_FLAGS = FileObserver.DELETE|FileObserver.MOVED_FROM; + private static final int MONITORED_EVENTS = FileObserver.CREATE|FileObserver.MOVED_TO| + FileObserver.CLOSE_WRITE|FileObserver.DELETE|FileObserver.MOVED_FROM; - public RuleObserver(File monitoredFile) { - super(monitoredFile.getParentFile().getAbsolutePath(), CREATED_FLAGS|DELETED_FLAGS); - mMonitoredFile = monitoredFile.getName(); + public RuleObserver(File monitoredDir) { + super(monitoredDir.getAbsolutePath(), MONITORED_EVENTS); } @Override public void onEvent(int event, String path) { - if (path.equals(mMonitoredFile)) { + if (path.endsWith(".xml")) { // we wait 250ms before taking any action on an event, in order to dedup multiple // events. E.g. a delete event followed by a create event followed by a subsequent - // write+close event; - if ((event & CREATED_FLAGS) != 0) { - mHandler.removeMessages(READ_RULES); - mHandler.removeMessages(CLEAR_RULES); - mHandler.sendEmptyMessageDelayed(READ_RULES, 250); - } else if ((event & DELETED_FLAGS) != 0) { - mHandler.removeMessages(READ_RULES); - mHandler.removeMessages(CLEAR_RULES); - mHandler.sendEmptyMessageDelayed(CLEAR_RULES, 250); - } + // write+close event + mHandler.removeMessages(0); + mHandler.sendEmptyMessageDelayed(0, 250); } } } @@ -509,4 +597,5 @@ public class IntentFirewall { return false; } } + } diff --git a/services/java/com/android/server/firewall/NotFilter.java b/services/java/com/android/server/firewall/NotFilter.java index f0fc337c5be2..09bf629a0df3 100644 --- a/services/java/com/android/server/firewall/NotFilter.java +++ b/services/java/com/android/server/firewall/NotFilter.java @@ -16,8 +16,8 @@ package com.android.server.firewall; +import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; import com.android.internal.util.XmlUtils; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -32,10 +32,10 @@ class NotFilter implements Filter { } @Override - public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, - int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { - return !mChild.matches(ifw, intent, callerApp, callerUid, callerPid, resolvedType, - resolvedApp); + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { + return !mChild.matches(ifw, resolvedComponent, intent, callerUid, callerPid, resolvedType, + receivingUid); } public static final FilterFactory FACTORY = new FilterFactory("not") { diff --git a/services/java/com/android/server/firewall/OrFilter.java b/services/java/com/android/server/firewall/OrFilter.java index 72db31e2edf1..f6a6f22278e4 100644 --- a/services/java/com/android/server/firewall/OrFilter.java +++ b/services/java/com/android/server/firewall/OrFilter.java @@ -16,8 +16,8 @@ package com.android.server.firewall; +import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -25,11 +25,11 @@ import java.io.IOException; class OrFilter extends FilterList { @Override - public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, - int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { for (int i=0; i<children.size(); i++) { - if (children.get(i).matches(ifw, intent, callerApp, callerUid, callerPid, resolvedType, - resolvedApp)) { + if (children.get(i).matches(ifw, resolvedComponent, intent, callerUid, callerPid, + resolvedType, receivingUid)) { return true; } } diff --git a/services/java/com/android/server/firewall/PortFilter.java b/services/java/com/android/server/firewall/PortFilter.java index fe7e085f0b5d..84ace553ee78 100644 --- a/services/java/com/android/server/firewall/PortFilter.java +++ b/services/java/com/android/server/firewall/PortFilter.java @@ -16,8 +16,8 @@ package com.android.server.firewall; +import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.net.Uri; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -41,8 +41,8 @@ class PortFilter implements Filter { } @Override - public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, - int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { int port = -1; Uri uri = intent.getData(); if (uri != null) { diff --git a/services/java/com/android/server/firewall/SenderFilter.java b/services/java/com/android/server/firewall/SenderFilter.java index 58bdd7318c08..c0eee69b4b0d 100644 --- a/services/java/com/android/server/firewall/SenderFilter.java +++ b/services/java/com/android/server/firewall/SenderFilter.java @@ -16,9 +16,14 @@ package com.android.server.firewall; +import android.app.AppGlobals; +import android.content.ComponentName; import android.content.Intent; import android.content.pm.ApplicationInfo; +import android.content.pm.IPackageManager; import android.os.Process; +import android.os.RemoteException; +import android.util.Slog; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -32,15 +37,21 @@ class SenderFilter { private static final String VAL_SYSTEM_OR_SIGNATURE = "system|signature"; private static final String VAL_USER_ID = "userId"; - static boolean isSystemApp(ApplicationInfo callerApp, int callerUid, int callerPid) { - if (callerUid == Process.SYSTEM_UID || - callerPid == Process.myPid()) { + static boolean isPrivilegedApp(int callerUid, int callerPid) { + if (callerUid == Process.SYSTEM_UID || callerUid == 0 || + callerPid == Process.myPid() || callerPid == 0) { return true; } - if (callerApp == null) { - return false; + + IPackageManager pm = AppGlobals.getPackageManager(); + try { + return (pm.getFlagsForUid(callerUid) & ApplicationInfo.FLAG_PRIVILEGED) != 0; + } catch (RemoteException ex) { + Slog.e(IntentFirewall.TAG, "Remote exception while retrieving uid flags", + ex); } - return (callerApp.flags & ApplicationInfo.FLAG_SYSTEM) != 0; + + return false; } public static final FilterFactory FACTORY = new FilterFactory("sender") { @@ -67,45 +78,38 @@ class SenderFilter { private static final Filter SIGNATURE = new Filter() { @Override - public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, - int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { - if (callerApp == null) { - return false; - } - return ifw.signaturesMatch(callerUid, resolvedApp.uid); + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { + return ifw.signaturesMatch(callerUid, receivingUid); } }; private static final Filter SYSTEM = new Filter() { @Override - public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, - int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { - if (callerApp == null) { - // if callerApp is null, the caller is the system process - return false; - } - return isSystemApp(callerApp, callerUid, callerPid); + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { + return isPrivilegedApp(callerUid, callerPid); } }; private static final Filter SYSTEM_OR_SIGNATURE = new Filter() { @Override - public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, - int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { - return isSystemApp(callerApp, callerUid, callerPid) || - ifw.signaturesMatch(callerUid, resolvedApp.uid); + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { + return isPrivilegedApp(callerUid, callerPid) || + ifw.signaturesMatch(callerUid, receivingUid); } }; private static final Filter USER_ID = new Filter() { @Override - public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, - int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { // This checks whether the caller is either the system process, or has the same user id // I.e. the same app, or an app that uses the same shared user id. // This is the same set of applications that would be able to access the component if // it wasn't exported. - return ifw.checkComponentPermission(null, callerPid, callerUid, resolvedApp.uid, false); + return ifw.checkComponentPermission(null, callerPid, callerUid, receivingUid, false); } }; } diff --git a/services/java/com/android/server/firewall/SenderPermissionFilter.java b/services/java/com/android/server/firewall/SenderPermissionFilter.java index 310da20eebf5..caa65f36157a 100644 --- a/services/java/com/android/server/firewall/SenderPermissionFilter.java +++ b/services/java/com/android/server/firewall/SenderPermissionFilter.java @@ -16,8 +16,8 @@ package com.android.server.firewall; +import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; @@ -33,12 +33,12 @@ class SenderPermissionFilter implements Filter { } @Override - public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, - int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { // We assume the component is exported here. If the component is not exported, then // ActivityManager would only resolve to this component for callers from the same uid. // In this case, it doesn't matter whether the component is exported or not. - return ifw.checkComponentPermission(mPermission, callerPid, callerUid, resolvedApp.uid, + return ifw.checkComponentPermission(mPermission, callerPid, callerUid, receivingUid, true); } diff --git a/services/java/com/android/server/firewall/StringFilter.java b/services/java/com/android/server/firewall/StringFilter.java index ed5d3f316083..28e99b372d54 100644 --- a/services/java/com/android/server/firewall/StringFilter.java +++ b/services/java/com/android/server/firewall/StringFilter.java @@ -18,7 +18,6 @@ package com.android.server.firewall; import android.content.ComponentName; import android.content.Intent; -import android.content.pm.ApplicationInfo; import android.net.Uri; import android.os.PatternMatcher; import org.xmlpull.v1.XmlPullParser; @@ -119,9 +118,9 @@ abstract class StringFilter implements Filter { protected abstract boolean matchesValue(String value); @Override - public boolean matches(IntentFirewall ifw, Intent intent, ApplicationInfo callerApp, - int callerUid, int callerPid, String resolvedType, ApplicationInfo resolvedApp) { - String value = mValueProvider.getValue(intent, callerApp, resolvedType, resolvedApp); + public boolean matches(IntentFirewall ifw, ComponentName resolvedComponent, Intent intent, + int callerUid, int callerPid, String resolvedType, int receivingUid) { + String value = mValueProvider.getValue(resolvedComponent, intent, resolvedType); return matchesValue(value); } @@ -135,8 +134,8 @@ abstract class StringFilter implements Filter { return StringFilter.readFromXml(this, parser); } - public abstract String getValue(Intent intent, ApplicationInfo callerApp, - String resolvedType, ApplicationInfo resolvedApp); + public abstract String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType); } private static class EqualsFilter extends StringFilter { @@ -230,11 +229,10 @@ abstract class StringFilter implements Filter { public static final ValueProvider COMPONENT = new ValueProvider("component") { @Override - public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType, - ApplicationInfo resolvedApp) { - ComponentName cn = intent.getComponent(); - if (cn != null) { - return cn.flattenToString(); + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { + if (resolvedComponent != null) { + return resolvedComponent.flattenToString(); } return null; } @@ -242,11 +240,10 @@ abstract class StringFilter implements Filter { public static final ValueProvider COMPONENT_NAME = new ValueProvider("component-name") { @Override - public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType, - ApplicationInfo resolvedApp) { - ComponentName cn = intent.getComponent(); - if (cn != null) { - return cn.getClassName(); + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { + if (resolvedComponent != null) { + return resolvedComponent.getClassName(); } return null; } @@ -254,11 +251,10 @@ abstract class StringFilter implements Filter { public static final ValueProvider COMPONENT_PACKAGE = new ValueProvider("component-package") { @Override - public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType, - ApplicationInfo resolvedApp) { - ComponentName cn = intent.getComponent(); - if (cn != null) { - return cn.getPackageName(); + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { + if (resolvedComponent != null) { + return resolvedComponent.getPackageName(); } return null; } @@ -266,16 +262,16 @@ abstract class StringFilter implements Filter { public static final FilterFactory ACTION = new ValueProvider("action") { @Override - public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType, - ApplicationInfo resolvedApp) { + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { return intent.getAction(); } }; public static final ValueProvider DATA = new ValueProvider("data") { @Override - public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType, - ApplicationInfo resolvedApp) { + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { Uri data = intent.getData(); if (data != null) { return data.toString(); @@ -286,16 +282,16 @@ abstract class StringFilter implements Filter { public static final ValueProvider MIME_TYPE = new ValueProvider("mime-type") { @Override - public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType, - ApplicationInfo resolvedApp) { + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { return resolvedType; } }; public static final ValueProvider SCHEME = new ValueProvider("scheme") { @Override - public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType, - ApplicationInfo resolvedApp) { + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { Uri data = intent.getData(); if (data != null) { return data.getScheme(); @@ -306,8 +302,8 @@ abstract class StringFilter implements Filter { public static final ValueProvider SSP = new ValueProvider("scheme-specific-part") { @Override - public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType, - ApplicationInfo resolvedApp) { + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { Uri data = intent.getData(); if (data != null) { return data.getSchemeSpecificPart(); @@ -318,8 +314,8 @@ abstract class StringFilter implements Filter { public static final ValueProvider HOST = new ValueProvider("host") { @Override - public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType, - ApplicationInfo resolvedApp) { + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { Uri data = intent.getData(); if (data != null) { return data.getHost(); @@ -330,8 +326,8 @@ abstract class StringFilter implements Filter { public static final ValueProvider PATH = new ValueProvider("path") { @Override - public String getValue(Intent intent, ApplicationInfo callerApp, String resolvedType, - ApplicationInfo resolvedApp) { + public String getValue(ComponentName resolvedComponent, Intent intent, + String resolvedType) { Uri data = intent.getData(); if (data != null) { return data.getPath(); diff --git a/services/java/com/android/server/input/InputManagerService.java b/services/java/com/android/server/input/InputManagerService.java index 5e4907e6820d..d749e6c626a4 100644 --- a/services/java/com/android/server/input/InputManagerService.java +++ b/services/java/com/android/server/input/InputManagerService.java @@ -283,7 +283,7 @@ public class InputManagerService extends IInputManager.Stub } // TODO(BT) Pass in paramter for bluetooth system - public void systemReady() { + public void systemRunning() { if (DEBUG) { Slog.d(TAG, "System ready."); } @@ -1292,8 +1292,9 @@ public class InputManagerService extends IInputManager.Stub // Native callback. private long notifyANR(InputApplicationHandle inputApplicationHandle, - InputWindowHandle inputWindowHandle) { - return mWindowManagerCallbacks.notifyANR(inputApplicationHandle, inputWindowHandle); + InputWindowHandle inputWindowHandle, String reason) { + return mWindowManagerCallbacks.notifyANR( + inputApplicationHandle, inputWindowHandle, reason); } // Native callback. @@ -1477,7 +1478,7 @@ public class InputManagerService extends IInputManager.Stub public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle); public long notifyANR(InputApplicationHandle inputApplicationHandle, - InputWindowHandle inputWindowHandle); + InputWindowHandle inputWindowHandle, String reason); public int interceptKeyBeforeQueueing(KeyEvent event, int policyFlags, boolean isScreenOn); diff --git a/services/java/com/android/server/input/InputWindowHandle.java b/services/java/com/android/server/input/InputWindowHandle.java index ad4fdd13cbb5..9eb9a3342f21 100644 --- a/services/java/com/android/server/input/InputWindowHandle.java +++ b/services/java/com/android/server/input/InputWindowHandle.java @@ -44,6 +44,7 @@ public final class InputWindowHandle { // Window layout params attributes. (WindowManager.LayoutParams) public int layoutParamsFlags; + public int layoutParamsPrivateFlags; public int layoutParamsType; // Dispatching timeout. diff --git a/services/java/com/android/server/location/FlpHardwareProvider.java b/services/java/com/android/server/location/FlpHardwareProvider.java new file mode 100644 index 000000000000..fab84a85ae0c --- /dev/null +++ b/services/java/com/android/server/location/FlpHardwareProvider.java @@ -0,0 +1,427 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location; + +import android.hardware.location.GeofenceHardware; +import android.hardware.location.GeofenceHardwareImpl; +import android.hardware.location.GeofenceHardwareRequestParcelable; +import android.hardware.location.IFusedLocationHardware; +import android.hardware.location.IFusedLocationHardwareSink; +import android.location.IFusedGeofenceHardware; +import android.location.FusedBatchOptions; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.location.LocationRequest; + +import android.content.Context; +import android.os.Bundle; +import android.os.Looper; +import android.os.RemoteException; +import android.os.SystemClock; +import android.util.Log; + +/** + * This class is an interop layer for JVM types and the JNI code that interacts + * with the FLP HAL implementation. + * + * {@hide} + */ +public class FlpHardwareProvider { + private GeofenceHardwareImpl mGeofenceHardwareSink = null; + private IFusedLocationHardwareSink mLocationSink = null; + + private static FlpHardwareProvider sSingletonInstance = null; + + private final static String TAG = "FlpHardwareProvider"; + private final Context mContext; + private final Object mLocationSinkLock = new Object(); + + // FlpHal result codes, they must be equal to the ones in fused_location.h + private static final int FLP_RESULT_SUCCESS = 0; + private static final int FLP_RESULT_ERROR = -1; + private static final int FLP_RESULT_INSUFFICIENT_MEMORY = -2; + private static final int FLP_RESULT_TOO_MANY_GEOFENCES = -3; + private static final int FLP_RESULT_ID_EXISTS = -4; + private static final int FLP_RESULT_ID_UNKNOWN = -5; + private static final int FLP_RESULT_INVALID_GEOFENCE_TRANSITION = -6; + + public static FlpHardwareProvider getInstance(Context context) { + if (sSingletonInstance == null) { + sSingletonInstance = new FlpHardwareProvider(context); + } + + return sSingletonInstance; + } + + private FlpHardwareProvider(Context context) { + mContext = context; + + // register for listening for passive provider data + LocationManager manager = (LocationManager) mContext.getSystemService( + Context.LOCATION_SERVICE); + final long minTime = 0; + final float minDistance = 0; + final boolean oneShot = false; + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + LocationManager.PASSIVE_PROVIDER, + minTime, + minDistance, + oneShot); + // Don't keep track of this request since it's done on behalf of other clients + // (which are kept track of separately). + request.setHideFromAppOps(true); + manager.requestLocationUpdates( + request, + new NetworkLocationListener(), + Looper.myLooper()); + } + + public static boolean isSupported() { + return nativeIsSupported(); + } + + /** + * Private callback functions used by FLP HAL. + */ + // FlpCallbacks members + private void onLocationReport(Location[] locations) { + for (Location location : locations) { + location.setProvider(LocationManager.FUSED_PROVIDER); + // set the elapsed time-stamp just as GPS provider does + location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); + } + + IFusedLocationHardwareSink sink; + synchronized (mLocationSinkLock) { + sink = mLocationSink; + } + try { + if (sink != null) { + sink.onLocationAvailable(locations); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling onLocationAvailable"); + } + } + + // FlpDiagnosticCallbacks members + private void onDataReport(String data) { + IFusedLocationHardwareSink sink; + synchronized (mLocationSinkLock) { + sink = mLocationSink; + } + try { + if (mLocationSink != null) { + sink.onDiagnosticDataAvailable(data); + } + } catch (RemoteException e) { + Log.e(TAG, "RemoteException calling onDiagnosticDataAvailable"); + } + } + + // FlpGeofenceCallbacks members + private void onGeofenceTransition( + int geofenceId, + Location location, + int transition, + long timestamp, + int sourcesUsed) { + getGeofenceHardwareSink().reportGeofenceTransition( + geofenceId, + updateLocationInformation(location), + transition, + timestamp, + GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE, + sourcesUsed); + } + + private void onGeofenceMonitorStatus(int status, int source, Location location) { + // allow the location to be optional in this event + Location updatedLocation = null; + if(location != null) { + updatedLocation = updateLocationInformation(location); + } + + getGeofenceHardwareSink().reportGeofenceMonitorStatus( + GeofenceHardware.MONITORING_TYPE_FUSED_HARDWARE, + status, + updatedLocation, + source); + } + + private void onGeofenceAdd(int geofenceId, int result) { + getGeofenceHardwareSink().reportGeofenceAddStatus( + geofenceId, + translateToGeofenceHardwareStatus(result)); + } + + private void onGeofenceRemove(int geofenceId, int result) { + getGeofenceHardwareSink().reportGeofenceRemoveStatus( + geofenceId, + translateToGeofenceHardwareStatus(result)); + } + + private void onGeofencePause(int geofenceId, int result) { + getGeofenceHardwareSink().reportGeofencePauseStatus( + geofenceId, + translateToGeofenceHardwareStatus(result)); + } + + private void onGeofenceResume(int geofenceId, int result) { + getGeofenceHardwareSink().reportGeofenceResumeStatus( + geofenceId, + translateToGeofenceHardwareStatus(result)); + } + + /** + * Private native methods accessing FLP HAL. + */ + static { nativeClassInit(); } + + // Core members + private static native void nativeClassInit(); + private static native boolean nativeIsSupported(); + + // FlpLocationInterface members + private native void nativeInit(); + private native int nativeGetBatchSize(); + private native void nativeStartBatching(int requestId, FusedBatchOptions options); + private native void nativeUpdateBatchingOptions(int requestId, FusedBatchOptions optionsObject); + private native void nativeStopBatching(int id); + private native void nativeRequestBatchedLocation(int lastNLocations); + private native void nativeInjectLocation(Location location); + // TODO [Fix] sort out the lifetime of the instance + private native void nativeCleanup(); + + // FlpDiagnosticsInterface members + private native boolean nativeIsDiagnosticSupported(); + private native void nativeInjectDiagnosticData(String data); + + // FlpDeviceContextInterface members + private native boolean nativeIsDeviceContextSupported(); + private native void nativeInjectDeviceContext(int deviceEnabledContext); + + // FlpGeofencingInterface members + private native boolean nativeIsGeofencingSupported(); + private native void nativeAddGeofences( + GeofenceHardwareRequestParcelable[] geofenceRequestsArray); + private native void nativePauseGeofence(int geofenceId); + private native void nativeResumeGeofence(int geofenceId, int monitorTransitions); + private native void nativeModifyGeofenceOption( + int geofenceId, + int lastTransition, + int monitorTransitions, + int notificationResponsiveness, + int unknownTimer, + int sourcesToUse); + private native void nativeRemoveGeofences(int[] geofenceIdsArray); + + /** + * Interface implementations for services built on top of this functionality. + */ + public static final String LOCATION = "Location"; + public static final String GEOFENCING = "Geofencing"; + + public IFusedLocationHardware getLocationHardware() { + nativeInit(); + return mLocationHardware; + } + + public IFusedGeofenceHardware getGeofenceHardware() { + nativeInit(); + return mGeofenceHardwareService; + } + + private final IFusedLocationHardware mLocationHardware = new IFusedLocationHardware.Stub() { + @Override + public void registerSink(IFusedLocationHardwareSink eventSink) { + synchronized (mLocationSinkLock) { + // only one sink is allowed at the moment + if (mLocationSink != null) { + throw new RuntimeException( + "IFusedLocationHardware does not support multiple sinks"); + } + + mLocationSink = eventSink; + } + } + + @Override + public void unregisterSink(IFusedLocationHardwareSink eventSink) { + synchronized (mLocationSinkLock) { + // don't throw if the sink is not registered, simply make it a no-op + if (mLocationSink == eventSink) { + mLocationSink = null; + } + } + } + + @Override + public int getSupportedBatchSize() { + return nativeGetBatchSize(); + } + + @Override + public void startBatching(int requestId, FusedBatchOptions options) { + nativeStartBatching(requestId, options); + } + + @Override + public void stopBatching(int requestId) { + nativeStopBatching(requestId); + } + + @Override + public void updateBatchingOptions(int requestId, FusedBatchOptions options) { + nativeUpdateBatchingOptions(requestId, options); + } + + @Override + public void requestBatchOfLocations(int batchSizeRequested) { + nativeRequestBatchedLocation(batchSizeRequested); + } + + @Override + public boolean supportsDiagnosticDataInjection() { + return nativeIsDiagnosticSupported(); + } + + @Override + public void injectDiagnosticData(String data) { + nativeInjectDiagnosticData(data); + } + + @Override + public boolean supportsDeviceContextInjection() { + return nativeIsDeviceContextSupported(); + } + + @Override + public void injectDeviceContext(int deviceEnabledContext) { + nativeInjectDeviceContext(deviceEnabledContext); + } + }; + + private final IFusedGeofenceHardware mGeofenceHardwareService = + new IFusedGeofenceHardware.Stub() { + @Override + public boolean isSupported() { + return nativeIsGeofencingSupported(); + } + + @Override + public void addGeofences(GeofenceHardwareRequestParcelable[] geofenceRequestsArray) { + nativeAddGeofences(geofenceRequestsArray); + } + + @Override + public void removeGeofences(int[] geofenceIds) { + nativeRemoveGeofences(geofenceIds); + } + + @Override + public void pauseMonitoringGeofence(int geofenceId) { + nativePauseGeofence(geofenceId); + } + + @Override + public void resumeMonitoringGeofence(int geofenceId, int monitorTransitions) { + nativeResumeGeofence(geofenceId, monitorTransitions); + } + + @Override + public void modifyGeofenceOptions(int geofenceId, + int lastTransition, + int monitorTransitions, + int notificationResponsiveness, + int unknownTimer, + int sourcesToUse) { + nativeModifyGeofenceOption( + geofenceId, + lastTransition, + monitorTransitions, + notificationResponsiveness, + unknownTimer, + sourcesToUse); + } + }; + + /** + * Internal classes and functions used by the provider. + */ + private final class NetworkLocationListener implements LocationListener { + @Override + public void onLocationChanged(Location location) { + if ( + !LocationManager.NETWORK_PROVIDER.equals(location.getProvider()) || + !location.hasAccuracy() + ) { + return; + } + + nativeInjectLocation(location); + } + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { } + + @Override + public void onProviderEnabled(String provider) { } + + @Override + public void onProviderDisabled(String provider) { } + } + + private GeofenceHardwareImpl getGeofenceHardwareSink() { + if (mGeofenceHardwareSink == null) { + mGeofenceHardwareSink = GeofenceHardwareImpl.getInstance(mContext); + } + + return mGeofenceHardwareSink; + } + + private static int translateToGeofenceHardwareStatus(int flpHalResult) { + switch(flpHalResult) { + case FLP_RESULT_SUCCESS: + return GeofenceHardware.GEOFENCE_SUCCESS; + case FLP_RESULT_ERROR: + return GeofenceHardware.GEOFENCE_FAILURE; + // TODO: uncomment this once the ERROR definition is marked public + //case FLP_RESULT_INSUFFICIENT_MEMORY: + // return GeofenceHardware.GEOFENCE_ERROR_INSUFFICIENT_MEMORY; + case FLP_RESULT_TOO_MANY_GEOFENCES: + return GeofenceHardware.GEOFENCE_ERROR_TOO_MANY_GEOFENCES; + case FLP_RESULT_ID_EXISTS: + return GeofenceHardware.GEOFENCE_ERROR_ID_EXISTS; + case FLP_RESULT_ID_UNKNOWN: + return GeofenceHardware.GEOFENCE_ERROR_ID_UNKNOWN; + case FLP_RESULT_INVALID_GEOFENCE_TRANSITION: + return GeofenceHardware.GEOFENCE_ERROR_INVALID_TRANSITION; + default: + Log.e(TAG, String.format("Invalid FlpHal result code: %d", flpHalResult)); + return GeofenceHardware.GEOFENCE_FAILURE; + } + } + + private Location updateLocationInformation(Location location) { + location.setProvider(LocationManager.FUSED_PROVIDER); + // set the elapsed time-stamp just as GPS provider does + location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); + return location; + } +} diff --git a/services/java/com/android/server/location/FusedLocationHardwareSecure.java b/services/java/com/android/server/location/FusedLocationHardwareSecure.java new file mode 100644 index 000000000000..389bd2477c84 --- /dev/null +++ b/services/java/com/android/server/location/FusedLocationHardwareSecure.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location; + +import android.content.Context; +import android.hardware.location.IFusedLocationHardware; +import android.hardware.location.IFusedLocationHardwareSink; +import android.location.FusedBatchOptions; +import android.os.RemoteException; + +/** + * FusedLocationHardware decorator that adds permission checking. + * @hide + */ +public class FusedLocationHardwareSecure extends IFusedLocationHardware.Stub { + private final IFusedLocationHardware mLocationHardware; + private final Context mContext; + private final String mPermissionId; + + public FusedLocationHardwareSecure( + IFusedLocationHardware locationHardware, + Context context, + String permissionId) { + mLocationHardware = locationHardware; + mContext = context; + mPermissionId = permissionId; + } + + private void checkPermissions() { + mContext.enforceCallingPermission( + mPermissionId, + String.format( + "Permission '%s' not granted to access FusedLocationHardware", + mPermissionId)); + } + + @Override + public void registerSink(IFusedLocationHardwareSink eventSink) throws RemoteException { + checkPermissions(); + mLocationHardware.registerSink(eventSink); + } + + @Override + public void unregisterSink(IFusedLocationHardwareSink eventSink) throws RemoteException { + checkPermissions(); + mLocationHardware.unregisterSink(eventSink); + } + + @Override + public int getSupportedBatchSize() throws RemoteException { + checkPermissions(); + return mLocationHardware.getSupportedBatchSize(); + } + + @Override + public void startBatching(int id, FusedBatchOptions batchOptions) throws RemoteException { + checkPermissions(); + mLocationHardware.startBatching(id, batchOptions); + } + + @Override + public void stopBatching(int id) throws RemoteException { + checkPermissions(); + mLocationHardware.stopBatching(id); + } + + @Override + public void updateBatchingOptions( + int id, + FusedBatchOptions batchoOptions + ) throws RemoteException { + checkPermissions(); + mLocationHardware.updateBatchingOptions(id, batchoOptions); + } + + @Override + public void requestBatchOfLocations(int batchSizeRequested) throws RemoteException { + checkPermissions(); + mLocationHardware.requestBatchOfLocations(batchSizeRequested); + } + + @Override + public boolean supportsDiagnosticDataInjection() throws RemoteException { + checkPermissions(); + return mLocationHardware.supportsDiagnosticDataInjection(); + } + + @Override + public void injectDiagnosticData(String data) throws RemoteException { + checkPermissions(); + mLocationHardware.injectDiagnosticData(data); + } + + @Override + public boolean supportsDeviceContextInjection() throws RemoteException { + checkPermissions(); + return mLocationHardware.supportsDeviceContextInjection(); + } + + @Override + public void injectDeviceContext(int deviceEnabledContext) throws RemoteException { + checkPermissions(); + mLocationHardware.injectDeviceContext(deviceEnabledContext); + } +} diff --git a/services/java/com/android/server/location/FusedProxy.java b/services/java/com/android/server/location/FusedProxy.java new file mode 100644 index 000000000000..f7fac774c979 --- /dev/null +++ b/services/java/com/android/server/location/FusedProxy.java @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (The "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.location; + +import com.android.server.ServiceWatcher; + +import android.Manifest; +import android.content.Context; +import android.hardware.location.IFusedLocationHardware; +import android.location.IFusedProvider; +import android.os.Handler; +import android.os.RemoteException; +import android.util.Log; + +/** + * Proxy that helps bind GCore FusedProvider implementations to the Fused Hardware instances. + * + * @hide + */ +public final class FusedProxy { + private final String TAG = "FusedProxy"; + private final ServiceWatcher mServiceWatcher; + private final FusedLocationHardwareSecure mLocationHardware; + + /** + * Constructor of the class. + * This is private as the class follows a factory pattern for construction. + * + * @param context The context needed for construction. + * @param handler The handler needed for construction. + * @param locationHardware The instance of the Fused location hardware assigned to the proxy. + */ + private FusedProxy( + Context context, + Handler handler, + IFusedLocationHardware locationHardware, + int overlaySwitchResId, + int defaultServicePackageNameResId, + int initialPackageNameResId) { + mLocationHardware = new FusedLocationHardwareSecure( + locationHardware, + context, + Manifest.permission.LOCATION_HARDWARE); + Runnable newServiceWork = new Runnable() { + @Override + public void run() { + bindProvider(mLocationHardware); + } + }; + + // prepare the connection to the provider + mServiceWatcher = new ServiceWatcher( + context, + TAG, + "com.android.location.service.FusedProvider", + overlaySwitchResId, + defaultServicePackageNameResId, + initialPackageNameResId, + newServiceWork, + handler); + } + + /** + * Creates an instance of the proxy and binds it to the appropriate FusedProvider. + * + * @param context The context needed for construction. + * @param handler The handler needed for construction. + * @param locationHardware The instance of the Fused location hardware assigned to the proxy. + * + * @return An instance of the proxy if it could be bound, null otherwise. + */ + public static FusedProxy createAndBind( + Context context, + Handler handler, + IFusedLocationHardware locationHardware, + int overlaySwitchResId, + int defaultServicePackageNameResId, + int initialPackageNameResId) { + FusedProxy fusedProxy = new FusedProxy( + context, + handler, + locationHardware, + overlaySwitchResId, + defaultServicePackageNameResId, + initialPackageNameResId); + + // try to bind the Fused provider + if (!fusedProxy.mServiceWatcher.start()) { + return null; + } + + return fusedProxy; + } + + /** + * Helper function to bind the FusedLocationHardware to the appropriate FusedProvider instance. + * + * @param locationHardware The FusedLocationHardware instance to use for the binding operation. + */ + private void bindProvider(IFusedLocationHardware locationHardware) { + IFusedProvider provider = IFusedProvider.Stub.asInterface(mServiceWatcher.getBinder()); + + if (provider == null) { + Log.e(TAG, "No instance of FusedProvider found on FusedLocationHardware connected."); + return; + } + + try { + provider.onFusedLocationHardwareChange(locationHardware); + } catch (RemoteException e) { + Log.e(TAG, e.toString()); + } + } +} diff --git a/services/java/com/android/server/location/GeofenceProxy.java b/services/java/com/android/server/location/GeofenceProxy.java index f6be27b6194b..bbc1f472387f 100644 --- a/services/java/com/android/server/location/GeofenceProxy.java +++ b/services/java/com/android/server/location/GeofenceProxy.java @@ -22,6 +22,7 @@ import android.hardware.location.GeofenceHardwareService; import android.hardware.location.IGeofenceHardware; import android.location.IGeofenceProvider; import android.location.IGpsGeofenceHardware; +import android.location.IFusedGeofenceHardware; import android.content.Context; import android.os.Handler; import android.os.IBinder; @@ -40,10 +41,15 @@ public final class GeofenceProxy { private static final String TAG = "GeofenceProxy"; private static final String SERVICE_ACTION = "com.android.location.service.GeofenceProvider"; - private ServiceWatcher mServiceWatcher; - private Context mContext; + private final ServiceWatcher mServiceWatcher; + private final Context mContext; + private final IGpsGeofenceHardware mGpsGeofenceHardware; + private final IFusedGeofenceHardware mFusedGeofenceHardware; + + private final Object mLock = new Object(); + + // Access to mGeofenceHardware needs to be synchronized by mLock. private IGeofenceHardware mGeofenceHardware; - private IGpsGeofenceHardware mGpsGeofenceHardware; private static final int GEOFENCE_PROVIDER_CONNECTED = 1; private static final int GEOFENCE_HARDWARE_CONNECTED = 2; @@ -60,9 +66,11 @@ public final class GeofenceProxy { public static GeofenceProxy createAndBind(Context context, int overlaySwitchResId, int defaultServicePackageNameResId, - int initialPackageNamesResId, Handler handler, IGpsGeofenceHardware gpsGeofence) { + int initialPackageNamesResId, Handler handler, IGpsGeofenceHardware gpsGeofence, + IFusedGeofenceHardware fusedGeofenceHardware) { GeofenceProxy proxy = new GeofenceProxy(context, overlaySwitchResId, - defaultServicePackageNameResId, initialPackageNamesResId, handler, gpsGeofence); + defaultServicePackageNameResId, initialPackageNamesResId, handler, gpsGeofence, + fusedGeofenceHardware); if (proxy.bindGeofenceProvider()) { return proxy; } else { @@ -72,11 +80,13 @@ public final class GeofenceProxy { private GeofenceProxy(Context context, int overlaySwitchResId, int defaultServicePackageNameResId, - int initialPackageNamesResId, Handler handler, IGpsGeofenceHardware gpsGeofence) { + int initialPackageNamesResId, Handler handler, IGpsGeofenceHardware gpsGeofence, + IFusedGeofenceHardware fusedGeofenceHardware) { mContext = context; mServiceWatcher = new ServiceWatcher(context, TAG, SERVICE_ACTION, overlaySwitchResId, defaultServicePackageNameResId, initialPackageNamesResId, mRunnable, handler); mGpsGeofenceHardware = gpsGeofence; + mFusedGeofenceHardware = fusedGeofenceHardware; bindHardwareGeofence(); } @@ -84,10 +94,6 @@ public final class GeofenceProxy { return mServiceWatcher.start(); } - private IGeofenceProvider getGeofenceProviderService() { - return IGeofenceProvider.Stub.asInterface(mServiceWatcher.getBinder()); - } - private void bindHardwareGeofence() { mContext.bindServiceAsUser(new Intent(mContext, GeofenceHardwareService.class), mServiceConnection, Context.BIND_AUTO_CREATE, UserHandle.OWNER); @@ -96,26 +102,34 @@ public final class GeofenceProxy { private ServiceConnection mServiceConnection = new ServiceConnection() { @Override public void onServiceConnected(ComponentName name, IBinder service) { - mGeofenceHardware = IGeofenceHardware.Stub.asInterface(service); - mHandler.sendEmptyMessage(GEOFENCE_HARDWARE_CONNECTED); + synchronized (mLock) { + mGeofenceHardware = IGeofenceHardware.Stub.asInterface(service); + mHandler.sendEmptyMessage(GEOFENCE_HARDWARE_CONNECTED); + } } @Override public void onServiceDisconnected(ComponentName name) { - mGeofenceHardware = null; - mHandler.sendEmptyMessage(GEOFENCE_HARDWARE_DISCONNECTED); + synchronized (mLock) { + mGeofenceHardware = null; + mHandler.sendEmptyMessage(GEOFENCE_HARDWARE_DISCONNECTED); + } } }; - private void setGeofenceHardwareInProvider() { + private void setGeofenceHardwareInProviderLocked() { try { - getGeofenceProviderService().setGeofenceHardware(mGeofenceHardware); + IGeofenceProvider provider = IGeofenceProvider.Stub.asInterface( + mServiceWatcher.getBinder()); + if (provider != null) { + provider.setGeofenceHardware(mGeofenceHardware); + } } catch (RemoteException e) { - Log.e(TAG, "Remote Exception: setGeofenceHardwareInProvider: " + e); + Log.e(TAG, "Remote Exception: setGeofenceHardwareInProviderLocked: " + e); } } - private void setGpsGeofence() { + private void setGpsGeofenceLocked() { try { mGeofenceHardware.setGpsGeofenceHardware(mGpsGeofenceHardware); } catch (RemoteException e) { @@ -123,33 +137,48 @@ public final class GeofenceProxy { } } + private void setFusedGeofenceLocked() { + try { + mGeofenceHardware.setFusedGeofenceHardware(mFusedGeofenceHardware); + } catch(RemoteException e) { + Log.e(TAG, "Error while connecting to GeofenceHardwareService"); + } + } // This needs to be reworked, when more services get added, // Might need a state machine or add a framework utility class, private Handler mHandler = new Handler() { - private boolean mGeofenceHardwareConnected = false; - private boolean mGeofenceProviderConnected = false; - @Override public void handleMessage(Message msg) { switch (msg.what) { case GEOFENCE_PROVIDER_CONNECTED: - mGeofenceProviderConnected = true; - if (mGeofenceHardwareConnected) { - setGeofenceHardwareInProvider(); + synchronized (mLock) { + if (mGeofenceHardware != null) { + setGeofenceHardwareInProviderLocked(); + } + // else: the geofence provider will be notified when the connection to + // GeofenceHardwareService is established. } break; case GEOFENCE_HARDWARE_CONNECTED: - setGpsGeofence(); - mGeofenceHardwareConnected = true; - if (mGeofenceProviderConnected) { - setGeofenceHardwareInProvider(); + synchronized (mLock) { + // Theoretically this won't happen because once the GeofenceHardwareService + // is connected to, we won't lose connection to it because it's a system + // service. But this check does make the code more robust. + if (mGeofenceHardware != null) { + setGpsGeofenceLocked(); + setFusedGeofenceLocked(); + setGeofenceHardwareInProviderLocked(); + } } break; case GEOFENCE_HARDWARE_DISCONNECTED: - mGeofenceHardwareConnected = false; - setGeofenceHardwareInProvider(); + synchronized (mLock) { + if (mGeofenceHardware == null) { + setGeofenceHardwareInProviderLocked(); + } + } break; } } diff --git a/services/java/com/android/server/location/GpsLocationProvider.java b/services/java/com/android/server/location/GpsLocationProvider.java index 8c88cabacd89..9c76c19051e8 100644 --- a/services/java/com/android/server/location/GpsLocationProvider.java +++ b/services/java/com/android/server/location/GpsLocationProvider.java @@ -24,9 +24,10 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.database.Cursor; +import android.hardware.location.GeofenceHardware; import android.hardware.location.GeofenceHardwareImpl; -import android.hardware.location.IGeofenceHardware; import android.location.Criteria; +import android.location.FusedBatchOptions; import android.location.IGpsGeofenceHardware; import android.location.IGpsStatusListener; import android.location.IGpsStatusProvider; @@ -41,6 +42,7 @@ import android.net.ConnectivityManager; import android.net.NetworkInfo; import android.net.Uri; import android.os.AsyncTask; +import android.os.BatteryStats; import android.os.Binder; import android.os.Bundle; import android.os.Handler; @@ -194,6 +196,17 @@ public class GpsLocationProvider implements LocationProviderInterface { private static final String PROPERTIES_FILE = "/etc/gps.conf"; + private static final int GPS_GEOFENCE_UNAVAILABLE = 1<<0L; + private static final int GPS_GEOFENCE_AVAILABLE = 1<<1L; + + // GPS Geofence errors. Should match gps.h constants. + private static final int GPS_GEOFENCE_OPERATION_SUCCESS = 0; + private static final int GPS_GEOFENCE_ERROR_TOO_MANY_GEOFENCES = 100; + private static final int GPS_GEOFENCE_ERROR_ID_EXISTS = -101; + private static final int GPS_GEOFENCE_ERROR_ID_UNKNOWN = -102; + private static final int GPS_GEOFENCE_ERROR_INVALID_TRANSITION = -103; + private static final int GPS_GEOFENCE_ERROR_GENERIC = -149; + /** simpler wrapper for ProviderRequest + Worksource */ private static class GpsRequest { public ProviderRequest request; @@ -456,7 +469,8 @@ public class GpsLocationProvider implements LocationProviderInterface { Context.APP_OPS_SERVICE)); // Battery statistics service to be notified when GPS turns on or off - mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService("batteryinfo")); + mBatteryStats = IBatteryStats.Stub.asInterface(ServiceManager.getService( + BatteryStats.SERVICE_NAME)); mProperties = new Properties(); try { @@ -498,8 +512,21 @@ public class GpsLocationProvider implements LocationProviderInterface { public void run() { LocationManager locManager = (LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE); - locManager.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, - 0, 0, new NetworkLocationListener(), mHandler.getLooper()); + final long minTime = 0; + final float minDistance = 0; + final boolean oneShot = false; + LocationRequest request = LocationRequest.createFromDeprecatedProvider( + LocationManager.PASSIVE_PROVIDER, + minTime, + minDistance, + oneShot); + // Don't keep track of this request since it's done on behalf of other clients + // (which are kept track of separately). + request.setHideFromAppOps(true); + locManager.requestLocationUpdates( + request, + new NetworkLocationListener(), + mHandler.getLooper()); } }); } @@ -890,7 +917,8 @@ public class GpsLocationProvider implements LocationProviderInterface { for (int i=0; i<newWork.size(); i++) { try { int uid = newWork.get(i); - mAppOpsService.startOperation(AppOpsManager.OP_GPS, uid, newWork.getName(i)); + mAppOpsService.startOperation(AppOpsManager.getToken(mAppOpsService), + AppOpsManager.OP_GPS, uid, newWork.getName(i)); if (uid != lastuid) { lastuid = uid; mBatteryStats.noteStartGps(uid); @@ -907,7 +935,8 @@ public class GpsLocationProvider implements LocationProviderInterface { for (int i=0; i<goneWork.size(); i++) { try { int uid = goneWork.get(i); - mAppOpsService.finishOperation(AppOpsManager.OP_GPS, uid, goneWork.getName(i)); + mAppOpsService.finishOperation(AppOpsManager.getToken(mAppOpsService), + AppOpsManager.OP_GPS, uid, goneWork.getName(i)); if (uid != lastuid) { lastuid = uid; mBatteryStats.noteStopGps(uid); @@ -1401,6 +1430,62 @@ public class GpsLocationProvider implements LocationProviderInterface { } /** + * Helper method to construct a location object. + */ + private Location buildLocation( + int flags, + double latitude, + double longitude, + double altitude, + float speed, + float bearing, + float accuracy, + long timestamp) { + Location location = new Location(LocationManager.GPS_PROVIDER); + if((flags & LOCATION_HAS_LAT_LONG) == LOCATION_HAS_LAT_LONG) { + location.setLatitude(latitude); + location.setLongitude(longitude); + location.setTime(timestamp); + location.setElapsedRealtimeNanos(SystemClock.elapsedRealtimeNanos()); + } + if((flags & LOCATION_HAS_ALTITUDE) == LOCATION_HAS_ALTITUDE) { + location.setAltitude(altitude); + } + if((flags & LOCATION_HAS_SPEED) == LOCATION_HAS_SPEED) { + location.setSpeed(speed); + } + if((flags & LOCATION_HAS_BEARING) == LOCATION_HAS_BEARING) { + location.setBearing(bearing); + } + if((flags & LOCATION_HAS_ACCURACY) == LOCATION_HAS_ACCURACY) { + location.setAccuracy(accuracy); + } + return location; + } + + /** + * Converts the GPS HAL status to the internal Geofence Hardware status. + */ + private int getGeofenceStatus(int status) { + switch(status) { + case GPS_GEOFENCE_OPERATION_SUCCESS: + return GeofenceHardware.GEOFENCE_SUCCESS; + case GPS_GEOFENCE_ERROR_GENERIC: + return GeofenceHardware.GEOFENCE_FAILURE; + case GPS_GEOFENCE_ERROR_ID_EXISTS: + return GeofenceHardware.GEOFENCE_ERROR_ID_EXISTS; + case GPS_GEOFENCE_ERROR_INVALID_TRANSITION: + return GeofenceHardware.GEOFENCE_ERROR_INVALID_TRANSITION; + case GPS_GEOFENCE_ERROR_TOO_MANY_GEOFENCES: + return GeofenceHardware.GEOFENCE_ERROR_TOO_MANY_GEOFENCES; + case GPS_GEOFENCE_ERROR_ID_UNKNOWN: + return GeofenceHardware.GEOFENCE_ERROR_ID_UNKNOWN; + default: + return -1; + } + } + + /** * Called from native to report GPS Geofence transition * All geofence callbacks are called on the same thread */ @@ -1410,8 +1495,22 @@ public class GpsLocationProvider implements LocationProviderInterface { if (mGeofenceHardwareImpl == null) { mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); } - mGeofenceHardwareImpl.reportGpsGeofenceTransition(geofenceId, flags, latitude, longitude, - altitude, speed, bearing, accuracy, timestamp, transition, transitionTimestamp); + Location location = buildLocation( + flags, + latitude, + longitude, + altitude, + speed, + bearing, + accuracy, + timestamp); + mGeofenceHardwareImpl.reportGeofenceTransition( + geofenceId, + location, + transition, + transitionTimestamp, + GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, + FusedBatchOptions.SourceTechnologies.GNSS); } /** @@ -1423,8 +1522,24 @@ public class GpsLocationProvider implements LocationProviderInterface { if (mGeofenceHardwareImpl == null) { mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); } - mGeofenceHardwareImpl.reportGpsGeofenceStatus(status, flags, latitude, longitude, altitude, - speed, bearing, accuracy, timestamp); + Location location = buildLocation( + flags, + latitude, + longitude, + altitude, + speed, + bearing, + accuracy, + timestamp); + int monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_UNAVAILABLE; + if(status == GPS_GEOFENCE_AVAILABLE) { + monitorStatus = GeofenceHardware.MONITOR_CURRENTLY_AVAILABLE; + } + mGeofenceHardwareImpl.reportGeofenceMonitorStatus( + GeofenceHardware.MONITORING_TYPE_GPS_HARDWARE, + monitorStatus, + location, + FusedBatchOptions.SourceTechnologies.GNSS); } /** @@ -1434,7 +1549,7 @@ public class GpsLocationProvider implements LocationProviderInterface { if (mGeofenceHardwareImpl == null) { mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); } - mGeofenceHardwareImpl.reportGpsGeofenceAddStatus(geofenceId, status); + mGeofenceHardwareImpl.reportGeofenceAddStatus(geofenceId, getGeofenceStatus(status)); } /** @@ -1444,7 +1559,7 @@ public class GpsLocationProvider implements LocationProviderInterface { if (mGeofenceHardwareImpl == null) { mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); } - mGeofenceHardwareImpl.reportGpsGeofenceRemoveStatus(geofenceId, status); + mGeofenceHardwareImpl.reportGeofenceRemoveStatus(geofenceId, getGeofenceStatus(status)); } /** @@ -1454,7 +1569,7 @@ public class GpsLocationProvider implements LocationProviderInterface { if (mGeofenceHardwareImpl == null) { mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); } - mGeofenceHardwareImpl.reportGpsGeofencePauseStatus(geofenceId, status); + mGeofenceHardwareImpl.reportGeofencePauseStatus(geofenceId, getGeofenceStatus(status)); } /** @@ -1464,7 +1579,7 @@ public class GpsLocationProvider implements LocationProviderInterface { if (mGeofenceHardwareImpl == null) { mGeofenceHardwareImpl = GeofenceHardwareImpl.getInstance(mContext); } - mGeofenceHardwareImpl.reportGpsGeofenceResumeStatus(geofenceId, status); + mGeofenceHardwareImpl.reportGeofenceResumeStatus(geofenceId, getGeofenceStatus(status)); } //============================================================= diff --git a/services/java/com/android/server/net/BaseNetworkObserver.java b/services/java/com/android/server/net/BaseNetworkObserver.java deleted file mode 100644 index 8b2aa5db81a0..000000000000 --- a/services/java/com/android/server/net/BaseNetworkObserver.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2011 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.android.server.net; - -import android.net.INetworkManagementEventObserver; - -/** - * Base {@link INetworkManagementEventObserver} that provides no-op - * implementations which can be overridden. - * - * @hide - */ -public class BaseNetworkObserver extends INetworkManagementEventObserver.Stub { - @Override - public void interfaceStatusChanged(String iface, boolean up) { - // default no-op - } - - @Override - public void interfaceRemoved(String iface) { - // default no-op - } - - @Override - public void interfaceLinkStateChanged(String iface, boolean up) { - // default no-op - } - - @Override - public void interfaceAdded(String iface) { - // default no-op - } - - @Override - public void interfaceClassDataActivityChanged(String label, boolean active) { - // default no-op - } - - @Override - public void limitReached(String limitName, String iface) { - // default no-op - } -} diff --git a/services/java/com/android/server/net/LockdownVpnTracker.java b/services/java/com/android/server/net/LockdownVpnTracker.java index e25192525e33..a2e9d6768696 100644 --- a/services/java/com/android/server/net/LockdownVpnTracker.java +++ b/services/java/com/android/server/net/LockdownVpnTracker.java @@ -26,6 +26,7 @@ import android.content.Context; import android.content.Intent; import android.content.IntentFilter; import android.net.LinkProperties; +import android.net.LinkAddress; import android.net.NetworkInfo; import android.net.NetworkInfo.DetailedState; import android.net.NetworkInfo.State; @@ -44,6 +45,8 @@ import com.android.server.ConnectivityService; import com.android.server.EventLogTags; import com.android.server.connectivity.Vpn; +import java.util.List; + /** * State tracker for lockdown mode. Watches for normal {@link NetworkInfo} to be * connected and kicks off VPN connection, managing any required {@code netd} @@ -73,7 +76,7 @@ public class LockdownVpnTracker { private String mAcceptedEgressIface; private String mAcceptedIface; - private String mAcceptedSourceAddr; + private List<LinkAddress> mAcceptedSourceAddr; private int mErrorCount; @@ -148,8 +151,13 @@ public class LockdownVpnTracker { showNotification(R.string.vpn_lockdown_connecting, R.drawable.vpn_disconnected); mAcceptedEgressIface = egressProp.getInterfaceName(); - mVpn.startLegacyVpn(mProfile, KeyStore.getInstance(), egressProp); - + try { + mVpn.startLegacyVpn(mProfile, KeyStore.getInstance(), egressProp); + } catch (IllegalStateException e) { + mAcceptedEgressIface = null; + Slog.e(TAG, "Failed to start VPN", e); + showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); + } } else { Slog.e(TAG, "Invalid VPN profile; requires IP-based server and DNS"); showNotification(R.string.vpn_lockdown_error, R.drawable.vpn_disconnected); @@ -157,14 +165,15 @@ public class LockdownVpnTracker { } else if (vpnInfo.isConnected() && vpnConfig != null) { final String iface = vpnConfig.interfaze; - final String sourceAddr = vpnConfig.addresses; + final List<LinkAddress> sourceAddrs = vpnConfig.addresses; if (TextUtils.equals(iface, mAcceptedIface) - && TextUtils.equals(sourceAddr, mAcceptedSourceAddr)) { + && sourceAddrs.equals(mAcceptedSourceAddr)) { return; } - Slog.d(TAG, "VPN connected using iface=" + iface + ", sourceAddr=" + sourceAddr); + Slog.d(TAG, "VPN connected using iface=" + iface + + ", sourceAddr=" + sourceAddrs.toString()); EventLogTags.writeLockdownVpnConnected(egressType); showNotification(R.string.vpn_lockdown_connected, R.drawable.vpn_connected); @@ -172,11 +181,13 @@ public class LockdownVpnTracker { clearSourceRulesLocked(); mNetService.setFirewallInterfaceRule(iface, true); - mNetService.setFirewallEgressSourceRule(sourceAddr, true); + for (LinkAddress addr : sourceAddrs) { + mNetService.setFirewallEgressSourceRule(addr.toString(), true); + } mErrorCount = 0; mAcceptedIface = iface; - mAcceptedSourceAddr = sourceAddr; + mAcceptedSourceAddr = sourceAddrs; } catch (RemoteException e) { throw new RuntimeException("Problem setting firewall rules", e); } @@ -258,7 +269,9 @@ public class LockdownVpnTracker { mAcceptedIface = null; } if (mAcceptedSourceAddr != null) { - mNetService.setFirewallEgressSourceRule(mAcceptedSourceAddr, false); + for (LinkAddress addr : mAcceptedSourceAddr) { + mNetService.setFirewallEgressSourceRule(addr.toString(), false); + } mAcceptedSourceAddr = null; } } catch (RemoteException e) { diff --git a/services/java/com/android/server/net/NetworkPolicyManagerService.java b/services/java/com/android/server/net/NetworkPolicyManagerService.java index a82f421c90ec..d568b11c70a1 100644 --- a/services/java/com/android/server/net/NetworkPolicyManagerService.java +++ b/services/java/com/android/server/net/NetworkPolicyManagerService.java @@ -274,7 +274,6 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { private final RemoteCallbackList<INetworkPolicyListener> mListeners = new RemoteCallbackList< INetworkPolicyListener>(); - private final HandlerThread mHandlerThread; private final Handler mHandler; private final AtomicFile mPolicyFile; @@ -306,9 +305,9 @@ public class NetworkPolicyManagerService extends INetworkPolicyManager.Stub { mNetworkManager = checkNotNull(networkManagement, "missing networkManagement"); mTime = checkNotNull(time, "missing TrustedTime"); - mHandlerThread = new HandlerThread(TAG); - mHandlerThread.start(); - mHandler = new Handler(mHandlerThread.getLooper(), mHandlerCallback); + HandlerThread thread = new HandlerThread(TAG); + thread.start(); + mHandler = new Handler(thread.getLooper(), mHandlerCallback); mSuppressDefaultPolicy = suppressDefaultPolicy; diff --git a/services/java/com/android/server/net/NetworkStatsRecorder.java b/services/java/com/android/server/net/NetworkStatsRecorder.java index 2b32b4150617..cea084b51849 100644 --- a/services/java/com/android/server/net/NetworkStatsRecorder.java +++ b/services/java/com/android/server/net/NetworkStatsRecorder.java @@ -135,6 +135,9 @@ public class NetworkStatsRecorder { } catch (IOException e) { Log.wtf(TAG, "problem completely reading network stats", e); recoverFromWtf(); + } catch (OutOfMemoryError e) { + Log.wtf(TAG, "problem completely reading network stats", e); + recoverFromWtf(); } } return complete; @@ -226,6 +229,9 @@ public class NetworkStatsRecorder { } catch (IOException e) { Log.wtf(TAG, "problem persisting pending stats", e); recoverFromWtf(); + } catch (OutOfMemoryError e) { + Log.wtf(TAG, "problem persisting pending stats", e); + recoverFromWtf(); } } } @@ -241,6 +247,9 @@ public class NetworkStatsRecorder { } catch (IOException e) { Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e); recoverFromWtf(); + } catch (OutOfMemoryError e) { + Log.wtf(TAG, "problem removing UIDs " + Arrays.toString(uids), e); + recoverFromWtf(); } // Remove any pending stats diff --git a/services/java/com/android/server/net/NetworkStatsService.java b/services/java/com/android/server/net/NetworkStatsService.java index 74be472d382d..5d6adc22685e 100644 --- a/services/java/com/android/server/net/NetworkStatsService.java +++ b/services/java/com/android/server/net/NetworkStatsService.java @@ -154,7 +154,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { private final Context mContext; private final INetworkManagementService mNetworkManager; - private final IAlarmManager mAlarmManager; + private final AlarmManager mAlarmManager; private final TrustedTime mTime; private final TelephonyManager mTeleManager; private final NetworkStatsSettings mSettings; @@ -240,7 +240,6 @@ public class NetworkStatsService extends INetworkStatsService.Stub { /** Data layer operation counters for splicing into other structures. */ private NetworkStats mUidOperations = new NetworkStats(0L, 10); - private final HandlerThread mHandlerThread; private final Handler mHandler; private boolean mSystemReady; @@ -262,18 +261,18 @@ public class NetworkStatsService extends INetworkStatsService.Stub { NetworkStatsSettings settings) { mContext = checkNotNull(context, "missing Context"); mNetworkManager = checkNotNull(networkManager, "missing INetworkManagementService"); - mAlarmManager = checkNotNull(alarmManager, "missing IAlarmManager"); mTime = checkNotNull(time, "missing TrustedTime"); mTeleManager = checkNotNull(TelephonyManager.getDefault(), "missing TelephonyManager"); mSettings = checkNotNull(settings, "missing NetworkStatsSettings"); + mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE); final PowerManager powerManager = (PowerManager) context.getSystemService( Context.POWER_SERVICE); mWakeLock = powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG); - mHandlerThread = new HandlerThread(TAG); - mHandlerThread.start(); - mHandler = new Handler(mHandlerThread.getLooper(), mHandlerCallback); + HandlerThread thread = new HandlerThread(TAG); + thread.start(); + mHandler = new Handler(thread.getLooper(), mHandlerCallback); mSystemDir = checkNotNull(systemDir); mBaseDir = new File(systemDir, "netstats"); @@ -415,6 +414,8 @@ public class NetworkStatsService extends INetworkStatsService.Stub { } } catch (IOException e) { Log.wtf(TAG, "problem during legacy upgrade", e); + } catch (OutOfMemoryError e) { + Log.wtf(TAG, "problem during legacy upgrade", e); } } @@ -423,20 +424,16 @@ public class NetworkStatsService extends INetworkStatsService.Stub { * reschedule based on current {@link NetworkStatsSettings#getPollInterval()}. */ private void registerPollAlarmLocked() { - try { - if (mPollIntent != null) { - mAlarmManager.remove(mPollIntent); - } + if (mPollIntent != null) { + mAlarmManager.cancel(mPollIntent); + } - mPollIntent = PendingIntent.getBroadcast( - mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0); + mPollIntent = PendingIntent.getBroadcast( + mContext, 0, new Intent(ACTION_NETWORK_STATS_POLL), 0); - final long currentRealtime = SystemClock.elapsedRealtime(); - mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime, - mSettings.getPollInterval(), mPollIntent); - } catch (RemoteException e) { - // ignored; service lives in system_server - } + final long currentRealtime = SystemClock.elapsedRealtime(); + mAlarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME, currentRealtime, + mSettings.getPollInterval(), mPollIntent); } /** @@ -1193,8 +1190,7 @@ public class NetworkStatsService extends INetworkStatsService.Stub { */ private NetworkStats getNetworkStatsTethering() throws RemoteException { try { - final String[] tetheredIfacePairs = mConnManager.getTetheredIfacePairs(); - return mNetworkManager.getNetworkStatsTethering(tetheredIfacePairs); + return mNetworkManager.getNetworkStatsTethering(); } catch (IllegalStateException e) { Log.wtf(TAG, "problem reading network stats", e); return new NetworkStats(0L, 10); diff --git a/services/java/com/android/server/pm/GrantedPermissions.java b/services/java/com/android/server/pm/GrantedPermissions.java index c7629b98635e..14258a46a564 100644 --- a/services/java/com/android/server/pm/GrantedPermissions.java +++ b/services/java/com/android/server/pm/GrantedPermissions.java @@ -44,6 +44,7 @@ class GrantedPermissions { void setFlags(int pkgFlags) { this.pkgFlags = pkgFlags & (ApplicationInfo.FLAG_SYSTEM + | ApplicationInfo.FLAG_PRIVILEGED | ApplicationInfo.FLAG_FORWARD_LOCK | ApplicationInfo.FLAG_EXTERNAL_STORAGE); } diff --git a/services/java/com/android/server/pm/KeySetManager.java b/services/java/com/android/server/pm/KeySetManager.java new file mode 100644 index 000000000000..66dc1d13c0e1 --- /dev/null +++ b/services/java/com/android/server/pm/KeySetManager.java @@ -0,0 +1,586 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import android.content.pm.KeySet; +import android.content.pm.PackageParser; +import android.os.Binder; +import android.util.Base64; +import android.util.Log; +import android.util.LongSparseArray; + +import java.io.IOException; +import java.io.PrintWriter; +import java.security.PublicKey; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserException; +import org.xmlpull.v1.XmlSerializer; + +/* + * Manages system-wide KeySet state. + */ +public class KeySetManager { + + static final String TAG = "KeySetManager"; + + /** Sentinel value returned when a {@code KeySet} is not found. */ + public static final long KEYSET_NOT_FOUND = -1; + + /** Sentinel value returned when public key is not found. */ + private static final long PUBLIC_KEY_NOT_FOUND = -1; + + private final Object mLockObject = new Object(); + + private final LongSparseArray<KeySet> mKeySets; + + private final LongSparseArray<PublicKey> mPublicKeys; + + private final LongSparseArray<Set<Long>> mKeySetMapping; + + private final Map<String, PackageSetting> mPackages; + + private static long lastIssuedKeySetId = 0; + + private static long lastIssuedKeyId = 0; + + public KeySetManager(Map<String, PackageSetting> packages) { + mKeySets = new LongSparseArray<KeySet>(); + mPublicKeys = new LongSparseArray<PublicKey>(); + mKeySetMapping = new LongSparseArray<Set<Long>>(); + mPackages = packages; + } + + /** + * Determine if a package is signed by the given KeySet. + * + * Returns false if the package was not signed by all the + * keys in the KeySet. + * + * Returns true if the package was signed by at least the + * keys in the given KeySet. + * + * Note that this can return true for multiple KeySets. + */ + public boolean packageIsSignedBy(String packageName, KeySet ks) { + synchronized (mLockObject) { + PackageSetting pkg = mPackages.get(packageName); + if (pkg == null) { + throw new NullPointerException("Invalid package name"); + } + if (pkg.keySetData == null) { + throw new NullPointerException("Package has no KeySet data"); + } + long id = getIdByKeySetLocked(ks); + return pkg.keySetData.packageIsSignedBy(id); + } + } + + /** + * This informs the system that the given package has defined a KeySet + * in its manifest that a) contains the given keys and b) is named + * alias by that package. + */ + public void addDefinedKeySetToPackage(String packageName, + Set<PublicKey> keys, String alias) { + if ((packageName == null) || (keys == null) || (alias == null)) { + //Log.d(TAG, "Got null argument for a defined keyset, ignoring!"); + return; + } + synchronized (mLockObject) { + KeySet ks = addKeySetLocked(keys); + PackageSetting pkg = mPackages.get(packageName); + if (pkg == null) { + throw new NullPointerException("Unknown package"); + } + long id = getIdByKeySetLocked(ks); + pkg.keySetData.addDefinedKeySet(id, alias); + } + } + + /** + * Similar to the above, this informs the system that the given package + * was signed by the provided KeySet. + */ + public void addSigningKeySetToPackage(String packageName, + Set<PublicKey> signingKeys) { + if ((packageName == null) || (signingKeys == null)) { + //Log.d(TAG, "Got null argument for a signing keyset, ignoring!"); + return; + } + synchronized (mLockObject) { + // add the signing KeySet + KeySet ks = addKeySetLocked(signingKeys); + long id = getIdByKeySetLocked(ks); + Set<Long> publicKeyIds = mKeySetMapping.get(id); + if (publicKeyIds == null) { + throw new NullPointerException("Got invalid KeySet id"); + } + + // attach it to the package + PackageSetting pkg = mPackages.get(packageName); + if (pkg == null) { + throw new NullPointerException("No such package!"); + } + pkg.keySetData.addSigningKeySet(id); + + // for each KeySet the package defines which is a subset of + // the one above, add the KeySet id to the package's signing KeySets + for (Long keySetID : pkg.keySetData.getDefinedKeySets()) { + Set<Long> definedKeys = mKeySetMapping.get(keySetID); + if (publicKeyIds.contains(definedKeys)) { + pkg.keySetData.addSigningKeySet(keySetID); + } + } + } + } + + /** + * Fetches the stable identifier associated with the given KeySet. Returns + * {@link #KEYSET_NOT_FOUND} if the KeySet... wasn't found. + */ + public long getIdByKeySet(KeySet ks) { + synchronized (mLockObject) { + return getIdByKeySetLocked(ks); + } + } + + private long getIdByKeySetLocked(KeySet ks) { + for (int keySetIndex = 0; keySetIndex < mKeySets.size(); keySetIndex++) { + KeySet value = mKeySets.valueAt(keySetIndex); + if (ks.equals(value)) { + return mKeySets.keyAt(keySetIndex); + } + } + return KEYSET_NOT_FOUND; + } + + /** + * Fetches the KeySet corresponding to the given stable identifier. + * + * Returns {@link #KEYSET_NOT_FOUND} if the identifier doesn't + * identify a {@link KeySet}. + */ + public KeySet getKeySetById(long id) { + synchronized (mLockObject) { + return mKeySets.get(id); + } + } + + /** + * Fetches the KeySet that a given package refers to by the provided alias. + * + * If the package isn't known to us, throws an IllegalArgumentException. + * Returns null if the alias isn't known to us. + */ + public KeySet getKeySetByAliasAndPackageName(String packageName, String alias) { + synchronized (mLockObject) { + PackageSetting p = mPackages.get(packageName); + if (p == null) { + throw new NullPointerException("Unknown package"); + } + if (p.keySetData == null) { + throw new IllegalArgumentException("Package has no keySet data"); + } + long keySetId = p.keySetData.getAliases().get(alias); + return mKeySets.get(keySetId); + } + } + + /** + * Fetches all the known {@link KeySet KeySets} that signed the given + * package. Returns {@code null} if package is unknown. + */ + public Set<KeySet> getSigningKeySetsByPackageName(String packageName) { + synchronized (mLockObject) { + Set<KeySet> signingKeySets = new HashSet<KeySet>(); + PackageSetting p = mPackages.get(packageName); + if (p == null) { + throw new NullPointerException("Unknown package"); + } + if (p.keySetData == null) { + throw new IllegalArgumentException("Package has no keySet data"); + } + for (long l : p.keySetData.getSigningKeySets()) { + signingKeySets.add(mKeySets.get(l)); + } + return signingKeySets; + } + } + + /** + * Creates a new KeySet corresponding to the given keys. + * + * If the {@link PublicKey PublicKeys} aren't known to the system, this + * adds them. Otherwise, they're deduped. + * + * If the KeySet isn't known to the system, this adds that and creates the + * mapping to the PublicKeys. If it is known, then it's deduped. + * + * Throws if the provided set is {@code null}. + */ + private KeySet addKeySetLocked(Set<PublicKey> keys) { + if (keys == null) { + throw new NullPointerException("Provided keys cannot be null"); + } + // add each of the keys in the provided set + Set<Long> addedKeyIds = new HashSet<Long>(keys.size()); + for (PublicKey k : keys) { + long id = addPublicKeyLocked(k); + addedKeyIds.add(id); + } + + // check to see if the resulting keyset is new + long existingKeySetId = getIdFromKeyIdsLocked(addedKeyIds); + if (existingKeySetId != KEYSET_NOT_FOUND) { + return mKeySets.get(existingKeySetId); + } + + // create the KeySet object + KeySet ks = new KeySet(new Binder()); + // get the first unoccupied slot in mKeySets + long id = getFreeKeySetIDLocked(); + // add the KeySet object to it + mKeySets.put(id, ks); + // add the stable key ids to the mapping + mKeySetMapping.put(id, addedKeyIds); + // go home + return ks; + } + + /** + * Adds the given PublicKey to the system, deduping as it goes. + */ + private long addPublicKeyLocked(PublicKey key) { + // check if the public key is new + long existingKeyId = getIdForPublicKeyLocked(key); + if (existingKeyId != PUBLIC_KEY_NOT_FOUND) { + return existingKeyId; + } + // if it's new find the first unoccupied slot in the public keys + long id = getFreePublicKeyIdLocked(); + // add the public key to it + mPublicKeys.put(id, key); + // return the stable identifier + return id; + } + + /** + * Finds the stable identifier for a KeySet based on a set of PublicKey stable IDs. + * + * Returns KEYSET_NOT_FOUND if there isn't one. + */ + private long getIdFromKeyIdsLocked(Set<Long> publicKeyIds) { + for (int keyMapIndex = 0; keyMapIndex < mKeySetMapping.size(); keyMapIndex++) { + Set<Long> value = mKeySetMapping.valueAt(keyMapIndex); + if (value.equals(publicKeyIds)) { + return mKeySetMapping.keyAt(keyMapIndex); + } + } + return KEYSET_NOT_FOUND; + } + + /** + * Finds the stable identifier for a PublicKey or PUBLIC_KEY_NOT_FOUND. + */ + private long getIdForPublicKeyLocked(PublicKey k) { + String encodedPublicKey = new String(k.getEncoded()); + for (int publicKeyIndex = 0; publicKeyIndex < mPublicKeys.size(); publicKeyIndex++) { + PublicKey value = mPublicKeys.valueAt(publicKeyIndex); + String encodedExistingKey = new String(value.getEncoded()); + if (encodedPublicKey.equals(encodedExistingKey)) { + return mPublicKeys.keyAt(publicKeyIndex); + } + } + return PUBLIC_KEY_NOT_FOUND; + } + + /** + * Gets an unused stable identifier for a KeySet. + */ + private long getFreeKeySetIDLocked() { + lastIssuedKeySetId += 1; + return lastIssuedKeySetId; + } + + /** + * Same as above, but for public keys. + */ + private long getFreePublicKeyIdLocked() { + lastIssuedKeyId += 1; + return lastIssuedKeyId; + } + + public void removeAppKeySetData(String packageName) { + synchronized (mLockObject) { + // Get the package's known keys and KeySets + Set<Long> deletableKeySets = getKnownKeySetsByPackageNameLocked(packageName); + Set<Long> deletableKeys = new HashSet<Long>(); + Set<Long> knownKeys = null; + for (Long ks : deletableKeySets) { + knownKeys = mKeySetMapping.get(ks); + if (knownKeys != null) { + deletableKeys.addAll(knownKeys); + } + } + + // Now remove the keys and KeySets known to any other package + for (String pkgName : mPackages.keySet()) { + if (pkgName.equals(packageName)) { + continue; + } + Set<Long> knownKeySets = getKnownKeySetsByPackageNameLocked(pkgName); + deletableKeySets.removeAll(knownKeySets); + knownKeys = new HashSet<Long>(); + for (Long ks : knownKeySets) { + knownKeys = mKeySetMapping.get(ks); + if (knownKeys != null) { + deletableKeys.removeAll(knownKeys); + } + } + } + + // The remaining keys and KeySets are not known to any other + // application and so can be safely deleted. + for (Long ks : deletableKeySets) { + mKeySets.delete(ks); + mKeySetMapping.delete(ks); + } + for (Long keyId : deletableKeys) { + mPublicKeys.delete(keyId); + } + + // Now remove them from the KeySets known to each package + for (String pkgName : mPackages.keySet()) { + PackageSetting p = mPackages.get(pkgName); + for (Long ks : deletableKeySets) { + p.keySetData.removeSigningKeySet(ks); + p.keySetData.removeDefinedKeySet(ks); + } + } + } + } + + private Set<Long> getKnownKeySetsByPackageNameLocked(String packageName) { + PackageSetting p = mPackages.get(packageName); + if (p == null) { + throw new NullPointerException("Unknown package"); + } + if (p.keySetData == null) { + throw new IllegalArgumentException("Package has no keySet data"); + } + Set<Long> knownKeySets = new HashSet<Long>(); + for (long ks : p.keySetData.getSigningKeySets()) { + knownKeySets.add(ks); + } + for (long ks : p.keySetData.getDefinedKeySets()) { + knownKeySets.add(ks); + } + return knownKeySets; + } + + public String encodePublicKey(PublicKey k) throws IOException { + return new String(Base64.encode(k.getEncoded(), 0)); + } + + public void dump(PrintWriter pw, String packageName, + PackageManagerService.DumpState dumpState) { + synchronized (mLockObject) { + boolean printedHeader = false; + for (Map.Entry<String, PackageSetting> e : mPackages.entrySet()) { + String keySetPackage = e.getKey(); + if (packageName != null && !packageName.equals(keySetPackage)) { + continue; + } + if (!printedHeader) { + if (dumpState.onTitlePrinted()) + pw.println(); + pw.println("Key Set Manager:"); + printedHeader = true; + } + PackageSetting pkg = e.getValue(); + pw.print(" ["); pw.print(keySetPackage); pw.println("]"); + if (pkg.keySetData != null) { + boolean printedLabel = false; + for (Map.Entry<String, Long> entry : pkg.keySetData.getAliases().entrySet()) { + if (!printedLabel) { + pw.print(" KeySets Aliases: "); + printedLabel = true; + } else { + pw.print(", "); + } + pw.print(entry.getKey()); + pw.print('='); + pw.print(Long.toString(entry.getValue())); + } + if (printedLabel) { + pw.println(""); + } + printedLabel = false; + for (long keySetId : pkg.keySetData.getDefinedKeySets()) { + if (!printedLabel) { + pw.print(" Defined KeySets: "); + printedLabel = true; + } else { + pw.print(", "); + } + pw.print(Long.toString(keySetId)); + } + if (printedLabel) { + pw.println(""); + } + printedLabel = false; + for (long keySetId : pkg.keySetData.getSigningKeySets()) { + if (!printedLabel) { + pw.print(" Signing KeySets: "); + printedLabel = true; + } else { + pw.print(", "); + } + pw.print(Long.toString(keySetId)); + } + if (printedLabel) { + pw.println(""); + } + } + } + } + } + + void writeKeySetManagerLPr(XmlSerializer serializer) throws IOException { + serializer.startTag(null, "keyset-settings"); + writePublicKeysLPr(serializer); + writeKeySetsLPr(serializer); + serializer.startTag(null, "lastIssuedKeyId"); + serializer.attribute(null, "value", Long.toString(lastIssuedKeyId)); + serializer.endTag(null, "lastIssuedKeyId"); + serializer.startTag(null, "lastIssuedKeySetId"); + serializer.attribute(null, "value", Long.toString(lastIssuedKeySetId)); + serializer.endTag(null, "lastIssuedKeySetId"); + serializer.endTag(null, "keyset-settings"); + } + + void writePublicKeysLPr(XmlSerializer serializer) throws IOException { + serializer.startTag(null, "keys"); + for (int pKeyIndex = 0; pKeyIndex < mPublicKeys.size(); pKeyIndex++) { + long id = mPublicKeys.keyAt(pKeyIndex); + PublicKey key = mPublicKeys.valueAt(pKeyIndex); + String encodedKey = encodePublicKey(key); + serializer.startTag(null, "public-key"); + serializer.attribute(null, "identifier", Long.toString(id)); + serializer.attribute(null, "value", encodedKey); + serializer.endTag(null, "public-key"); + } + serializer.endTag(null, "keys"); + } + + void writeKeySetsLPr(XmlSerializer serializer) throws IOException { + serializer.startTag(null, "keysets"); + for (int keySetIndex = 0; keySetIndex < mKeySetMapping.size(); keySetIndex++) { + long id = mKeySetMapping.keyAt(keySetIndex); + Set<Long> keys = mKeySetMapping.valueAt(keySetIndex); + serializer.startTag(null, "keyset"); + serializer.attribute(null, "identifier", Long.toString(id)); + for (long keyId : keys) { + serializer.startTag(null, "key-id"); + serializer.attribute(null, "identifier", Long.toString(keyId)); + serializer.endTag(null, "key-id"); + } + serializer.endTag(null, "keyset"); + } + serializer.endTag(null, "keysets"); + } + + void readKeySetsLPw(XmlPullParser parser) + throws XmlPullParserException, IOException { + int type; + long currentKeySetId = 0; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + final String tagName = parser.getName(); + if (tagName.equals("keys")) { + readKeysLPw(parser); + } else if (tagName.equals("keysets")) { + readKeySetListLPw(parser); + } + } + } + + void readKeysLPw(XmlPullParser parser) + throws XmlPullParserException, IOException { + int outerDepth = parser.getDepth(); + int type; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + final String tagName = parser.getName(); + if (tagName.equals("public-key")) { + readPublicKeyLPw(parser); + } else if (tagName.equals("lastIssuedKeyId")) { + lastIssuedKeyId = Long.parseLong(parser.getAttributeValue(null, "value")); + } else if (tagName.equals("lastIssuedKeySetId")) { + lastIssuedKeySetId = Long.parseLong(parser.getAttributeValue(null, "value")); + } + } + } + + void readKeySetListLPw(XmlPullParser parser) + throws XmlPullParserException, IOException { + int outerDepth = parser.getDepth(); + int type; + long currentKeySetId = 0; + while ((type = parser.next()) != XmlPullParser.END_DOCUMENT + && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) { + if (type == XmlPullParser.END_TAG || type == XmlPullParser.TEXT) { + continue; + } + final String tagName = parser.getName(); + if (tagName.equals("keyset")) { + currentKeySetId = readIdentifierLPw(parser); + mKeySets.put(currentKeySetId, new KeySet(new Binder())); + mKeySetMapping.put(currentKeySetId, new HashSet<Long>()); + } else if (tagName.equals("key-id")) { + long id = readIdentifierLPw(parser); + mKeySetMapping.get(currentKeySetId).add(id); + } + } + } + + long readIdentifierLPw(XmlPullParser parser) + throws XmlPullParserException { + return Long.parseLong(parser.getAttributeValue(null, "identifier")); + } + + void readPublicKeyLPw(XmlPullParser parser) + throws XmlPullParserException { + String encodedID = parser.getAttributeValue(null, "identifier"); + long identifier = Long.parseLong(encodedID); + String encodedPublicKey = parser.getAttributeValue(null, "value"); + PublicKey pub = PackageParser.parsePublicKey(encodedPublicKey); + if (pub != null) { + mPublicKeys.put(identifier, pub); + } + } +} diff --git a/services/java/com/android/server/pm/PackageKeySetData.java b/services/java/com/android/server/pm/PackageKeySetData.java new file mode 100644 index 000000000000..cb60621a851d --- /dev/null +++ b/services/java/com/android/server/pm/PackageKeySetData.java @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.pm; + +import java.util.Arrays; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +public class PackageKeySetData { + + private long[] mSigningKeySets; + + private long[] mDefinedKeySets; + + private final Map<String, Long> mKeySetAliases; + + PackageKeySetData() { + mSigningKeySets = new long[0]; + mDefinedKeySets = new long[0]; + mKeySetAliases = new HashMap<String, Long>(); + } + + PackageKeySetData(PackageKeySetData original) { + mSigningKeySets = original.getSigningKeySets().clone(); + mDefinedKeySets = original.getDefinedKeySets().clone(); + mKeySetAliases = new HashMap<String, Long>(); + mKeySetAliases.putAll(original.getAliases()); + } + + public void addSigningKeySet(long ks) { + // deduplicate + for (long knownKeySet : mSigningKeySets) { + if (ks == knownKeySet) { + return; + } + } + int end = mSigningKeySets.length; + mSigningKeySets = Arrays.copyOf(mSigningKeySets, end + 1); + mSigningKeySets[end] = ks; + } + + public void removeSigningKeySet(long ks) { + if (packageIsSignedBy(ks)) { + long[] keysets = new long[mSigningKeySets.length - 1]; + int index = 0; + for (long signingKeySet : mSigningKeySets) { + if (signingKeySet != ks) { + keysets[index] = signingKeySet; + index += 1; + } + } + mSigningKeySets = keysets; + } + } + + public void addDefinedKeySet(long ks, String alias) { + // deduplicate + for (long knownKeySet : mDefinedKeySets) { + if (ks == knownKeySet) { + return; + } + } + int end = mDefinedKeySets.length; + mDefinedKeySets = Arrays.copyOf(mDefinedKeySets, end + 1); + mDefinedKeySets[end] = ks; + mKeySetAliases.put(alias, ks); + } + + public void removeDefinedKeySet(long ks) { + if (mKeySetAliases.containsValue(ks)) { + long[] keysets = new long[mDefinedKeySets.length - 1]; + int index = 0; + for (long definedKeySet : mDefinedKeySets) { + if (definedKeySet != ks) { + keysets[index] = definedKeySet; + index += 1; + } + } + mDefinedKeySets = keysets; + for (String alias : mKeySetAliases.keySet()) { + if (mKeySetAliases.get(alias) == ks) { + mKeySetAliases.remove(alias); + break; + } + } + } + } + + public boolean packageIsSignedBy(long ks) { + for (long signingKeySet : mSigningKeySets) { + if (ks == signingKeySet) { + return true; + } + } + return false; + } + + public long[] getSigningKeySets() { + return mSigningKeySets; + } + + public long[] getDefinedKeySets() { + return mDefinedKeySets; + } + + public Map<String, Long> getAliases() { + return mKeySetAliases; + } +}
\ No newline at end of file diff --git a/services/java/com/android/server/pm/PackageManagerService.java b/services/java/com/android/server/pm/PackageManagerService.java index a84f900f99d6..2a93dfccdfab 100755 --- a/services/java/com/android/server/pm/PackageManagerService.java +++ b/services/java/com/android/server/pm/PackageManagerService.java @@ -35,12 +35,14 @@ import com.android.internal.app.IMediaContainerService; import com.android.internal.app.ResolverActivity; import com.android.internal.content.NativeLibraryHelper; import com.android.internal.content.PackageHelper; +import com.android.internal.util.FastPrintWriter; import com.android.internal.util.FastXmlSerializer; import com.android.internal.util.XmlUtils; import com.android.server.DeviceStorageMonitorService; import com.android.server.EventLogTags; import com.android.server.IntentResolver; +import com.android.server.Watchdog; import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlSerializer; @@ -89,6 +91,7 @@ import android.content.pm.ManifestDigest; import android.content.pm.VerificationParams; import android.content.pm.VerifierDeviceIdentity; import android.content.pm.VerifierInfo; +import android.content.res.Resources; import android.net.Uri; import android.os.Binder; import android.os.Build; @@ -112,13 +115,14 @@ import android.os.SystemProperties; import android.os.UserHandle; import android.os.Environment.UserEnvironment; import android.os.UserManager; -import android.provider.Settings.Secure; import android.security.KeyStore; import android.security.SystemKeyStore; +import android.text.TextUtils; import android.util.DisplayMetrics; import android.util.EventLog; import android.util.Log; import android.util.LogPrinter; +import android.util.PrintStreamPrinter; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; @@ -157,6 +161,8 @@ import libcore.io.IoUtils; import libcore.io.Libcore; import libcore.io.StructStat; +import com.android.internal.R; + /** * Keep track of all those .apks everywhere. * @@ -276,6 +282,9 @@ public class PackageManagerService extends IPackageManager.Stub { // This is the object monitoring the system app dir. final FileObserver mSystemInstallObserver; + // This is the object monitoring the privileged system app dir. + final FileObserver mPrivilegedInstallObserver; + // This is the object monitoring the system app dir. final FileObserver mVendorInstallObserver; @@ -289,11 +298,7 @@ public class PackageManagerService extends IPackageManager.Stub { // LOCK HELD. Can be called with mInstallLock held. final Installer mInstaller; - final File mFrameworkDir; - final File mSystemAppDir; - final File mVendorAppDir; final File mAppInstallDir; - final File mDalvikCacheDir; /** * Directory to which applications installed internally have native @@ -378,13 +383,12 @@ public class PackageManagerService extends IPackageManager.Stub { // All available services, for your resolving pleasure. final ServiceIntentResolver mServices = new ServiceIntentResolver(); - // Keys are String (provider class name), values are Provider. - final HashMap<ComponentName, PackageParser.Provider> mProvidersByComponent = - new HashMap<ComponentName, PackageParser.Provider>(); + // All available providers, for your resolving pleasure. + final ProviderIntentResolver mProviders = new ProviderIntentResolver(); // Mapping from provider base names (first directory in content URI codePath) // to the provider information. - final HashMap<String, PackageParser.Provider> mProviders = + final HashMap<String, PackageParser.Provider> mProvidersByAuthority = new HashMap<String, PackageParser.Provider>(); // Mapping from instrumentation class names to info about them. @@ -420,6 +424,9 @@ public class PackageManagerService extends IPackageManager.Stub { final ResolveInfo mResolveInfo = new ResolveInfo(); ComponentName mResolveComponentName; PackageParser.Package mPlatformPackage; + ComponentName mCustomResolverComponentName; + + boolean mResolverReplaced = false; // Set of pending broadcasts for aggregating enable/disable of components. static class PendingPackageBroadcasts { @@ -427,7 +434,7 @@ public class PackageManagerService extends IPackageManager.Stub { final SparseArray<HashMap<String, ArrayList<String>>> mUidMap; public PendingPackageBroadcasts() { - mUidMap = new SparseArray<HashMap<String, ArrayList<String>>>(); + mUidMap = new SparseArray<HashMap<String, ArrayList<String>>>(2); } public ArrayList<String> get(int userId, String packageName) { @@ -842,6 +849,19 @@ public class PackageManagerService extends IPackageManager.Stub { sendPackageBroadcast(Intent.ACTION_MY_PACKAGE_REPLACED, null, null, res.pkg.applicationInfo.packageName, null, updateUsers); + + // treat asec-hosted packages like removable media on upgrade + if (isForwardLocked(res.pkg) || isExternal(res.pkg)) { + if (DEBUG_INSTALL) { + Slog.i(TAG, "upgrading pkg " + res.pkg + + " is ASEC-hosted -> AVAILABLE"); + } + int[] uidArray = new int[] { res.pkg.applicationInfo.uid }; + ArrayList<String> pkgList = new ArrayList<String>(1); + pkgList.add(res.pkg.applicationInfo.packageName); + sendResourcesChangedBroadcast(true, false, + pkgList,uidArray, null); + } } if (res.removedInfo.args != null) { // Remove the replaced package's older resources safely now @@ -1054,13 +1074,18 @@ public class PackageManagerService extends IPackageManager.Stub { mNoDexOpt = "eng".equals(SystemProperties.get("ro.build.type")); mMetrics = new DisplayMetrics(); mSettings = new Settings(context); - mSettings.addSharedUserLPw("android.uid.system", - Process.SYSTEM_UID, ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID, ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLPw("android.uid.log", LOG_UID, ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID, ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID, ApplicationInfo.FLAG_SYSTEM); - mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID, ApplicationInfo.FLAG_SYSTEM); + mSettings.addSharedUserLPw("android.uid.system", Process.SYSTEM_UID, + ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); + mSettings.addSharedUserLPw("android.uid.phone", RADIO_UID, + ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); + mSettings.addSharedUserLPw("android.uid.log", LOG_UID, + ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); + mSettings.addSharedUserLPw("android.uid.nfc", NFC_UID, + ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); + mSettings.addSharedUserLPw("android.uid.bluetooth", BLUETOOTH_UID, + ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); + mSettings.addSharedUserLPw("android.uid.shell", SHELL_UID, + ApplicationInfo.FLAG_SYSTEM|ApplicationInfo.FLAG_PRIVILEGED); String separateProcesses = SystemProperties.get("debug.separate_processes"); if (separateProcesses != null && separateProcesses.length() > 0) { @@ -1090,6 +1115,7 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { mHandlerThread.start(); mHandler = new PackageHandler(mHandlerThread.getLooper()); + Watchdog.getInstance().addThread(mHandler, mHandlerThread.getName()); File dataDir = Environment.getDataDirectory(); mAppDataDir = new File(dataDir, "data"); @@ -1109,6 +1135,15 @@ public class PackageManagerService extends IPackageManager.Stub { mRestoredSettings = mSettings.readLPw(this, sUserManager.getUsers(false), mSdkVersion, mOnlyCore); + String customResolverActivity = Resources.getSystem().getString( + R.string.config_customResolverActivity); + if (TextUtils.isEmpty(customResolverActivity)) { + customResolverActivity = null; + } else { + mCustomResolverComponentName = ComponentName.unflattenFromString( + customResolverActivity); + } + long startTime = SystemClock.uptimeMillis(); EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_SYSTEM_SCAN_START, @@ -1122,40 +1157,27 @@ public class PackageManagerService extends IPackageManager.Stub { scanMode |= SCAN_NO_DEX; } - final HashSet<String> libFiles = new HashSet<String>(); - - mFrameworkDir = new File(Environment.getRootDirectory(), "framework"); - mDalvikCacheDir = new File(dataDir, "dalvik-cache"); - - boolean didDexOpt = false; + final HashSet<String> alreadyDexOpted = new HashSet<String>(); /** - * Out of paranoia, ensure that everything in the boot class - * path has been dexed. + * Add everything in the in the boot class path to the + * list of process files because dexopt will have been run + * if necessary during zygote startup. */ String bootClassPath = System.getProperty("java.boot.class.path"); if (bootClassPath != null) { String[] paths = splitString(bootClassPath, ':'); for (int i=0; i<paths.length; i++) { - try { - if (dalvik.system.DexFile.isDexOptNeeded(paths[i])) { - libFiles.add(paths[i]); - mInstaller.dexopt(paths[i], Process.SYSTEM_UID, true); - didDexOpt = true; - } - } catch (FileNotFoundException e) { - Slog.w(TAG, "Boot class path not found: " + paths[i]); - } catch (IOException e) { - Slog.w(TAG, "Cannot dexopt " + paths[i] + "; is it an APK or JAR? " - + e.getMessage()); - } + alreadyDexOpted.add(paths[i]); } } else { Slog.w(TAG, "No BOOTCLASSPATH found!"); } + boolean didDexOpt = false; + /** - * Also ensure all external libraries have had dexopt run on them. + * Ensure all external libraries have had dexopt run on them. */ if (mSharedLibraries.size() > 0) { Iterator<SharedLibraryEntry> libs = mSharedLibraries.values().iterator(); @@ -1166,7 +1188,7 @@ public class PackageManagerService extends IPackageManager.Stub { } try { if (dalvik.system.DexFile.isDexOptNeeded(lib)) { - libFiles.add(lib); + alreadyDexOpted.add(lib); mInstaller.dexopt(lib, Process.SYSTEM_UID, true); didDexOpt = true; } @@ -1179,22 +1201,29 @@ public class PackageManagerService extends IPackageManager.Stub { } } + File frameworkDir = new File(Environment.getRootDirectory(), "framework"); + // Gross hack for now: we know this file doesn't contain any // code, so don't dexopt it to avoid the resulting log spew. - libFiles.add(mFrameworkDir.getPath() + "/framework-res.apk"); + alreadyDexOpted.add(frameworkDir.getPath() + "/framework-res.apk"); + + // Gross hack for now: we know this file is only part of + // the boot class path for art, so don't dexopt it to + // avoid the resulting log spew. + alreadyDexOpted.add(frameworkDir.getPath() + "/core-libart.jar"); /** * And there are a number of commands implemented in Java, which * we currently need to do the dexopt on so that they can be * run from a non-root shell. */ - String[] frameworkFiles = mFrameworkDir.list(); + String[] frameworkFiles = frameworkDir.list(); if (frameworkFiles != null) { for (int i=0; i<frameworkFiles.length; i++) { - File libPath = new File(mFrameworkDir, frameworkFiles[i]); + File libPath = new File(frameworkDir, frameworkFiles[i]); String path = libPath.getPath(); // Skip the file if we alrady did it. - if (libFiles.contains(path)) { + if (alreadyDexOpted.contains(path)) { continue; } // Skip the file if it is not a type we want to dexopt. @@ -1215,19 +1244,21 @@ public class PackageManagerService extends IPackageManager.Stub { } if (didDexOpt) { + File dalvikCacheDir = new File(dataDir, "dalvik-cache"); + // If we had to do a dexopt of one of the previous // things, then something on the system has changed. // Consider this significant, and wipe away all other // existing dexopt files to ensure we don't leave any // dangling around. - String[] files = mDalvikCacheDir.list(); + String[] files = dalvikCacheDir.list(); if (files != null) { for (int i=0; i<files.length; i++) { String fn = files[i]; if (fn.startsWith("data@app@") || fn.startsWith("data@app-private@")) { Slog.i(TAG, "Pruning dalvik file: " + fn); - (new File(mDalvikCacheDir, fn)).delete(); + (new File(dalvikCacheDir, fn)).delete(); } } } @@ -1235,26 +1266,35 @@ public class PackageManagerService extends IPackageManager.Stub { // Find base frameworks (resource packages without code). mFrameworkInstallObserver = new AppDirObserver( - mFrameworkDir.getPath(), OBSERVER_EVENTS, true); + frameworkDir.getPath(), OBSERVER_EVENTS, true, false); mFrameworkInstallObserver.startWatching(); - scanDirLI(mFrameworkDir, PackageParser.PARSE_IS_SYSTEM + scanDirLI(frameworkDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode | SCAN_NO_DEX, 0); - // Collect all system packages. - mSystemAppDir = new File(Environment.getRootDirectory(), "app"); + // Collected privileged system packages. + File privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app"); + mPrivilegedInstallObserver = new AppDirObserver( + privilegedAppDir.getPath(), OBSERVER_EVENTS, true, true); + mPrivilegedInstallObserver.startWatching(); + scanDirLI(privilegedAppDir, PackageParser.PARSE_IS_SYSTEM + | PackageParser.PARSE_IS_SYSTEM_DIR + | PackageParser.PARSE_IS_PRIVILEGED, scanMode, 0); + + // Collect ordinary system packages. + File systemAppDir = new File(Environment.getRootDirectory(), "app"); mSystemInstallObserver = new AppDirObserver( - mSystemAppDir.getPath(), OBSERVER_EVENTS, true); + systemAppDir.getPath(), OBSERVER_EVENTS, true, false); mSystemInstallObserver.startWatching(); - scanDirLI(mSystemAppDir, PackageParser.PARSE_IS_SYSTEM + scanDirLI(systemAppDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0); // Collect all vendor packages. - mVendorAppDir = new File("/vendor/app"); + File vendorAppDir = new File("/vendor/app"); mVendorInstallObserver = new AppDirObserver( - mVendorAppDir.getPath(), OBSERVER_EVENTS, true); + vendorAppDir.getPath(), OBSERVER_EVENTS, true, false); mVendorInstallObserver.startWatching(); - scanDirLI(mVendorAppDir, PackageParser.PARSE_IS_SYSTEM + scanDirLI(vendorAppDir, PackageParser.PARSE_IS_SYSTEM | PackageParser.PARSE_IS_SYSTEM_DIR, scanMode, 0); if (DEBUG_UPGRADE) Log.v(TAG, "Running installd update commands"); @@ -1321,16 +1361,19 @@ public class PackageManagerService extends IPackageManager.Stub { //delete tmp files deleteTempPackageFiles(); + // Remove any shared userIDs that have no associated packages + mSettings.pruneSharedUsersLPw(); + if (!mOnlyCore) { EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_DATA_SCAN_START, SystemClock.uptimeMillis()); mAppInstallObserver = new AppDirObserver( - mAppInstallDir.getPath(), OBSERVER_EVENTS, false); + mAppInstallDir.getPath(), OBSERVER_EVENTS, false, false); mAppInstallObserver.startWatching(); scanDirLI(mAppInstallDir, 0, scanMode, 0); mDrmAppInstallObserver = new AppDirObserver( - mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, false); + mDrmAppPrivateInstallDir.getPath(), OBSERVER_EVENTS, false, false); mDrmAppInstallObserver.startWatching(); scanDirLI(mDrmAppPrivateInstallDir, PackageParser.PARSE_FORWARD_LOCK, scanMode, 0); @@ -1470,7 +1513,7 @@ public class PackageManagerService extends IPackageManager.Stub { return super.onTransact(code, data, reply, flags); } catch (RuntimeException e) { if (!(e instanceof SecurityException) && !(e instanceof IllegalArgumentException)) { - Slog.e(TAG, "Package Manager Crash", e); + Slog.wtf(TAG, "Package Manager Crash", e); } throw e; } @@ -1781,8 +1824,8 @@ public class PackageManagerService extends IPackageManager.Stub { } } + @Override public int[] getPackageGids(String packageName) { - final boolean enforcedDefault = isPermissionEnforcedDefault(READ_EXTERNAL_STORAGE); // reader synchronized (mPackages) { PackageParser.Package p = mPackages.get(packageName); @@ -1790,17 +1833,7 @@ public class PackageManagerService extends IPackageManager.Stub { Log.v(TAG, "getPackageGids" + packageName + ": " + p); if (p != null) { final PackageSetting ps = (PackageSetting)p.mExtras; - final SharedUserSetting suid = ps.sharedUser; - int[] gids = suid != null ? suid.gids : ps.gids; - - // include GIDs for any unenforced permissions - if (!isPermissionEnforcedLocked(READ_EXTERNAL_STORAGE, enforcedDefault)) { - final BasePermission basePerm = mSettings.mPermissions.get( - READ_EXTERNAL_STORAGE); - gids = appendInts(gids, basePerm.gids); - } - - return gids; + return ps.getGids(); } } // stupid thing to indicate an error. @@ -1913,8 +1946,6 @@ public class PackageManagerService extends IPackageManager.Stub { getDataPathForPackage(packageName, 0).getPath(); pkg.applicationInfo.nativeLibraryDir = ps.nativeLibraryPathString; } - // pkg.mSetEnabled = ps.getEnabled(userId); - // pkg.mSetStopped = ps.getStopped(userId); return generatePackageInfo(pkg, flags, userId); } return null; @@ -2063,7 +2094,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (!sUserManager.exists(userId)) return null; enforceCrossUserPermission(Binder.getCallingUid(), userId, false, "get provider info"); synchronized (mPackages) { - PackageParser.Provider p = mProvidersByComponent.get(component); + PackageParser.Provider p = mProviders.mProviders.get(component); if (DEBUG_PACKAGE_INFO) Log.v( TAG, "getProviderInfo " + component + ": " + p); if (p != null && mSettings.isEnabledLPr(p.info, flags, userId)) { @@ -2123,7 +2154,6 @@ public class PackageManagerService extends IPackageManager.Stub { } public int checkPermission(String permName, String pkgName) { - final boolean enforcedDefault = isPermissionEnforcedDefault(permName); synchronized (mPackages) { PackageParser.Package p = mPackages.get(pkgName); if (p != null && p.mExtras != null) { @@ -2136,15 +2166,11 @@ public class PackageManagerService extends IPackageManager.Stub { return PackageManager.PERMISSION_GRANTED; } } - if (!isPermissionEnforcedLocked(permName, enforcedDefault)) { - return PackageManager.PERMISSION_GRANTED; - } } return PackageManager.PERMISSION_DENIED; } public int checkUidPermission(String permName, int uid) { - final boolean enforcedDefault = isPermissionEnforcedDefault(permName); synchronized (mPackages) { Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid)); if (obj != null) { @@ -2158,9 +2184,6 @@ public class PackageManagerService extends IPackageManager.Stub { return PackageManager.PERMISSION_GRANTED; } } - if (!isPermissionEnforcedLocked(permName, enforcedDefault)) { - return PackageManager.PERMISSION_GRANTED; - } } return PackageManager.PERMISSION_DENIED; } @@ -2565,6 +2588,20 @@ public class PackageManagerService extends IPackageManager.Stub { } } + public int getFlagsForUid(int uid) { + synchronized (mPackages) { + Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid)); + if (obj instanceof SharedUserSetting) { + final SharedUserSetting sus = (SharedUserSetting) obj; + return sus.pkgFlags; + } else if (obj instanceof PackageSetting) { + final PackageSetting ps = (PackageSetting) obj; + return ps.pkgFlags; + } + } + return 0; + } + @Override public ResolveInfo resolveIntent(Intent intent, String resolvedType, int flags, int userId) { @@ -2574,6 +2611,37 @@ public class PackageManagerService extends IPackageManager.Stub { return chooseBestActivity(intent, resolvedType, flags, query, userId); } + @Override + public void setLastChosenActivity(Intent intent, String resolvedType, int flags, + IntentFilter filter, int match, ComponentName activity) { + final int userId = UserHandle.getCallingUserId(); + if (DEBUG_PREFERRED) { + Log.v(TAG, "setLastChosenActivity intent=" + intent + + " resolvedType=" + resolvedType + + " flags=" + flags + + " filter=" + filter + + " match=" + match + + " activity=" + activity); + filter.dump(new PrintStreamPrinter(System.out), " "); + } + intent.setComponent(null); + List<ResolveInfo> query = queryIntentActivities(intent, resolvedType, flags, userId); + // Find any earlier preferred or last chosen entries and nuke them + findPreferredActivity(intent, resolvedType, + flags, query, 0, false, true, false, userId); + // Add the new activity as the last chosen for this filter + addPreferredActivityInternal(filter, match, null, activity, false, userId); + } + + @Override + public ResolveInfo getLastChosenActivity(Intent intent, String resolvedType, int flags) { + final int userId = UserHandle.getCallingUserId(); + if (DEBUG_PREFERRED) Log.v(TAG, "Querying last chosen activity for " + intent); + List<ResolveInfo> query = queryIntentActivities(intent, resolvedType, flags, userId); + return findPreferredActivity(intent, resolvedType, flags, query, 0, + false, false, false, userId); + } + private ResolveInfo chooseBestActivity(Intent intent, String resolvedType, int flags, List<ResolveInfo> query, int userId) { if (query != null) { @@ -2581,12 +2649,13 @@ public class PackageManagerService extends IPackageManager.Stub { if (N == 1) { return query.get(0); } else if (N > 1) { + final boolean debug = ((intent.getFlags() & Intent.FLAG_DEBUG_LOG_RESOLUTION) != 0); // If there is more than one activity with the same priority, // then let the user decide between them. ResolveInfo r0 = query.get(0); ResolveInfo r1 = query.get(1); - if (DEBUG_INTENT_MATCHING) { - Log.d(TAG, r0.activityInfo.name + "=" + r0.priority + " vs " + if (DEBUG_INTENT_MATCHING || debug) { + Slog.v(TAG, r0.activityInfo.name + "=" + r0.priority + " vs " + r1.activityInfo.name + "=" + r1.priority); } // If the first activity has a higher priority, or a different @@ -2599,7 +2668,7 @@ public class PackageManagerService extends IPackageManager.Stub { // If we have saved a preference for a preferred activity for // this Intent, use that. ResolveInfo ri = findPreferredActivity(intent, resolvedType, - flags, query, r0.priority, userId); + flags, query, r0.priority, true, false, debug, userId); if (ri != null) { return ri; } @@ -2618,16 +2687,19 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } - ResolveInfo findPreferredActivity(Intent intent, String resolvedType, - int flags, List<ResolveInfo> query, int priority, int userId) { + ResolveInfo findPreferredActivity(Intent intent, String resolvedType, int flags, + List<ResolveInfo> query, int priority, boolean always, + boolean removeMatches, boolean debug, int userId) { if (!sUserManager.exists(userId)) return null; // writer synchronized (mPackages) { if (intent.getSelector() != null) { - intent = intent.getSelector(); + intent = intent.getSelector(); } if (DEBUG_PREFERRED) intent.addFlags(Intent.FLAG_DEBUG_LOG_RESOLUTION); PreferredIntentResolver pir = mSettings.mPreferredActivities.get(userId); + // Get the list of preferred activities that handle the intent + if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Looking for preferred activities..."); List<PreferredActivity> prefs = pir != null ? pir.queryIntent(intent, resolvedType, (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId) @@ -2638,41 +2710,50 @@ public class PackageManagerService extends IPackageManager.Stub { // from the same match quality. int match = 0; - if (DEBUG_PREFERRED) { - Log.v(TAG, "Figuring out best match..."); - } + if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Figuring out best match..."); final int N = query.size(); for (int j=0; j<N; j++) { final ResolveInfo ri = query.get(j); - if (DEBUG_PREFERRED) { - Log.v(TAG, "Match for " + ri.activityInfo + ": 0x" - + Integer.toHexString(match)); - } + if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Match for " + ri.activityInfo + + ": 0x" + Integer.toHexString(match)); if (ri.match > match) { match = ri.match; } } - if (DEBUG_PREFERRED) { - Log.v(TAG, "Best match: 0x" + Integer.toHexString(match)); - } + if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Best match: 0x" + + Integer.toHexString(match)); match &= IntentFilter.MATCH_CATEGORY_MASK; final int M = prefs.size(); for (int i=0; i<M; i++) { final PreferredActivity pa = prefs.get(i); + if (DEBUG_PREFERRED || debug) { + Slog.v(TAG, "Checking PreferredActivity ds=" + + (pa.countDataSchemes() > 0 ? pa.getDataScheme(0) : "<none>") + + "\n component=" + pa.mPref.mComponent); + pa.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " "); + } if (pa.mPref.mMatch != match) { + if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping bad match " + + Integer.toHexString(pa.mPref.mMatch)); + continue; + } + // If it's not an "always" type preferred activity and that's what we're + // looking for, skip it. + if (always && !pa.mPref.mAlways) { + if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Skipping mAlways=false entry"); continue; } final ActivityInfo ai = getActivityInfo(pa.mPref.mComponent, flags | PackageManager.GET_DISABLED_COMPONENTS, userId); - if (DEBUG_PREFERRED) { - Log.v(TAG, "Got preferred activity:"); + if (DEBUG_PREFERRED || debug) { + Slog.v(TAG, "Found preferred activity:"); if (ai != null) { - ai.dump(new LogPrinter(Log.VERBOSE, TAG), " "); + ai.dump(new LogPrinter(Log.VERBOSE, TAG, Log.LOG_ID_SYSTEM), " "); } else { - Log.v(TAG, " null"); + Slog.v(TAG, " null"); } } if (ai == null) { @@ -2696,23 +2777,45 @@ public class PackageManagerService extends IPackageManager.Stub { continue; } - // Okay we found a previously set preferred app. + if (removeMatches) { + pir.removeFilter(pa); + if (DEBUG_PREFERRED) { + Slog.v(TAG, "Removing match " + pa.mPref.mComponent); + } + break; + } + + // Okay we found a previously set preferred or last chosen app. // If the result set is different from when this // was created, we need to clear it and re-ask the - // user their preference. - if (!pa.mPref.sameSet(query, priority)) { + // user their preference, if we're looking for an "always" type entry. + if (always && !pa.mPref.sameSet(query, priority)) { Slog.i(TAG, "Result set changed, dropping preferred activity for " + intent + " type " + resolvedType); + if (DEBUG_PREFERRED) { + Slog.v(TAG, "Removing preferred activity since set changed " + + pa.mPref.mComponent); + } pir.removeFilter(pa); + // Re-add the filter as a "last chosen" entry (!always) + PreferredActivity lastChosen = new PreferredActivity( + pa, pa.mPref.mMatch, null, pa.mPref.mComponent, false); + pir.addFilter(lastChosen); + mSettings.writePackageRestrictionsLPr(userId); return null; } - // Yay! + // Yay! Either the set matched or we're looking for the last chosen + if (DEBUG_PREFERRED || debug) Slog.v(TAG, "Returning preferred activity: " + + ri.activityInfo.packageName + "/" + ri.activityInfo.name); + mSettings.writePackageRestrictionsLPr(userId); return ri; } } } + mSettings.writePackageRestrictionsLPr(userId); } + if (DEBUG_PREFERRED || debug) Slog.v(TAG, "No preferred activity to return"); return null; } @@ -3017,6 +3120,43 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + public List<ResolveInfo> queryIntentContentProviders( + Intent intent, String resolvedType, int flags, int userId) { + if (!sUserManager.exists(userId)) return Collections.emptyList(); + ComponentName comp = intent.getComponent(); + if (comp == null) { + if (intent.getSelector() != null) { + intent = intent.getSelector(); + comp = intent.getComponent(); + } + } + if (comp != null) { + final List<ResolveInfo> list = new ArrayList<ResolveInfo>(1); + final ProviderInfo pi = getProviderInfo(comp, flags, userId); + if (pi != null) { + final ResolveInfo ri = new ResolveInfo(); + ri.providerInfo = pi; + list.add(ri); + } + return list; + } + + // reader + synchronized (mPackages) { + String pkgName = intent.getPackage(); + if (pkgName == null) { + return mProviders.queryIntent(intent, resolvedType, flags, userId); + } + final PackageParser.Package pkg = mPackages.get(pkgName); + if (pkg != null) { + return mProviders.queryIntentForPackage( + intent, resolvedType, flags, pkg.providers, userId); + } + return null; + } + } + + @Override public ParceledListSlice<PackageInfo> getInstalledPackages(int flags, int userId) { final boolean listUninstalled = (flags & PackageManager.GET_UNINSTALLED_PACKAGES) != 0; @@ -3189,7 +3329,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (!sUserManager.exists(userId)) return null; // reader synchronized (mPackages) { - final PackageParser.Provider provider = mProviders.get(name); + final PackageParser.Provider provider = mProvidersByAuthority.get(name); PackageSetting ps = provider != null ? mSettings.mPackages.get(provider.owner.packageName) : null; @@ -3210,8 +3350,8 @@ public class PackageManagerService extends IPackageManager.Stub { public void querySyncProviders(List<String> outNames, List<ProviderInfo> outInfo) { // reader synchronized (mPackages) { - final Iterator<Map.Entry<String, PackageParser.Provider>> i = mProviders.entrySet() - .iterator(); + final Iterator<Map.Entry<String, PackageParser.Provider>> i = mProvidersByAuthority + .entrySet().iterator(); final int userId = UserHandle.getCallingUserId(); while (i.hasNext()) { Map.Entry<String, PackageParser.Provider> entry = i.next(); @@ -3235,10 +3375,9 @@ public class PackageManagerService extends IPackageManager.Stub { public List<ProviderInfo> queryContentProviders(String processName, int uid, int flags) { ArrayList<ProviderInfo> finalList = null; - // reader synchronized (mPackages) { - final Iterator<PackageParser.Provider> i = mProvidersByComponent.values().iterator(); + final Iterator<PackageParser.Provider> i = mProviders.mProviders.values().iterator(); final int userId = processName != null ? UserHandle.getUserId(uid) : UserHandle.getCallingUserId(); while (i.hasNext()) { @@ -3311,7 +3450,8 @@ public class PackageManagerService extends IPackageManager.Stub { } if (DEBUG_PACKAGE_SCANNING) { - Log.d(TAG, "Scanning app dir " + dir); + Log.d(TAG, "Scanning app dir " + dir + " scanMode=" + scanMode + + " flags=0x" + Integer.toHexString(flags)); } int i; @@ -3344,7 +3484,7 @@ public class PackageManagerService extends IPackageManager.Stub { try { File fname = getSettingsProblemFile(); FileOutputStream out = new FileOutputStream(fname, true); - PrintWriter pw = new PrintWriter(out); + PrintWriter pw = new FastPrintWriter(out); SimpleDateFormat formatter = new SimpleDateFormat(); String dateString = formatter.format(new Date(System.currentTimeMillis())); pw.println(dateString + ": " + msg); @@ -3400,10 +3540,12 @@ public class PackageManagerService extends IPackageManager.Stub { pp.setOnlyCoreApps(mOnlyCore); final PackageParser.Package pkg = pp.parsePackage(scanFile, scanPath, mMetrics, parseFlags); + if (pkg == null) { mLastScanError = pp.getParseError(); return null; } + PackageSetting ps = null; PackageSetting updatedPkg; // reader @@ -3553,6 +3695,7 @@ public class PackageManagerService extends IPackageManager.Stub { } else { resPath = pkg.mScanPath; } + codePath = pkg.mScanPath; // Set application objects path explicitly. setApplicationInfoPaths(pkg, codePath, resPath); @@ -3967,6 +4110,15 @@ public class PackageManagerService extends IPackageManager.Stub { pkg.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM; } + if ((parseFlags&PackageParser.PARSE_IS_PRIVILEGED) != 0) { + pkg.applicationInfo.flags |= ApplicationInfo.FLAG_PRIVILEGED; + } + + if (mCustomResolverComponentName != null && + mCustomResolverComponentName.getPackageName().equals(pkg.packageName)) { + setUpCustomResolverActivity(pkg); + } + if (pkg.packageName.equals("android")) { synchronized (mPackages) { if (mAndroidApplication != null) { @@ -3978,26 +4130,28 @@ public class PackageManagerService extends IPackageManager.Stub { return null; } - // Set up information for our fall-back user intent resolution - // activity. + // Set up information for our fall-back user intent resolution activity. mPlatformPackage = pkg; pkg.mVersionCode = mSdkVersion; mAndroidApplication = pkg.applicationInfo; - mResolveActivity.applicationInfo = mAndroidApplication; - mResolveActivity.name = ResolverActivity.class.getName(); - mResolveActivity.packageName = mAndroidApplication.packageName; - mResolveActivity.processName = "system:ui"; - mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; - mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; - mResolveActivity.theme = com.android.internal.R.style.Theme_Holo_Dialog_Alert; - mResolveActivity.exported = true; - mResolveActivity.enabled = true; - mResolveInfo.activityInfo = mResolveActivity; - mResolveInfo.priority = 0; - mResolveInfo.preferredOrder = 0; - mResolveInfo.match = 0; - mResolveComponentName = new ComponentName( - mAndroidApplication.packageName, mResolveActivity.name); + + if (!mResolverReplaced) { + mResolveActivity.applicationInfo = mAndroidApplication; + mResolveActivity.name = ResolverActivity.class.getName(); + mResolveActivity.packageName = mAndroidApplication.packageName; + mResolveActivity.processName = "system:ui"; + mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; + mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS; + mResolveActivity.theme = com.android.internal.R.style.Theme_Holo_Dialog_Alert; + mResolveActivity.exported = true; + mResolveActivity.enabled = true; + mResolveInfo.activityInfo = mResolveActivity; + mResolveInfo.priority = 0; + mResolveInfo.preferredOrder = 0; + mResolveInfo.match = 0; + mResolveComponentName = new ComponentName( + mAndroidApplication.packageName, mResolveActivity.name); + } } } @@ -4042,8 +4196,7 @@ public class PackageManagerService extends IPackageManager.Stub { } if (pkg.mSharedUserId != null) { - suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, - pkg.applicationInfo.flags, true); + suid = mSettings.getSharedUserLPw(pkg.mSharedUserId, 0, true); if (suid == null) { Slog.w(TAG, "Creating application package " + pkg.packageName + " for shared user failed"); @@ -4196,8 +4349,8 @@ public class PackageManagerService extends IPackageManager.Stub { if (p.info.authority != null) { String names[] = p.info.authority.split(";"); for (int j = 0; j < names.length; j++) { - if (mProviders.containsKey(names[j])) { - PackageParser.Provider other = mProviders.get(names[j]); + if (mProvidersByAuthority.containsKey(names[j])) { + PackageParser.Provider other = mProvidersByAuthority.get(names[j]); Slog.w(TAG, "Can't install because provider name " + names[j] + " (in package " + pkg.applicationInfo.packageName + ") is already used by " @@ -4539,8 +4692,22 @@ public class PackageManagerService extends IPackageManager.Stub { // so that we do not end up in a confused state while the user is still using the older // version of the application while the new one gets installed. if ((parseFlags & PackageManager.INSTALL_REPLACE_EXISTING) != 0) { + // If the package lives in an asec, tell everyone that the container is going + // away so they can clean up any references to its resources (which would prevent + // vold from being able to unmount the asec) + if (isForwardLocked(pkg) || isExternal(pkg)) { + if (DEBUG_INSTALL) { + Slog.i(TAG, "upgrading pkg " + pkg + " is ASEC-hosted -> UNAVAILABLE"); + } + final int[] uidArray = new int[] { pkg.applicationInfo.uid }; + final ArrayList<String> pkgList = new ArrayList<String>(1); + pkgList.add(pkg.applicationInfo.packageName); + sendResourcesChangedBroadcast(false, true, pkgList, uidArray, null); + } + + // Post the request that it be killed now that the going-away broadcast is en route killApplication(pkg.applicationInfo.packageName, - pkg.applicationInfo.uid); + pkg.applicationInfo.uid, "update pkg"); } // Also need to kill any apps that are dependent on the library. @@ -4548,7 +4715,7 @@ public class PackageManagerService extends IPackageManager.Stub { for (int i=0; i<clientLibPkgs.size(); i++) { PackageParser.Package clientPkg = clientLibPkgs.get(i); killApplication(clientPkg.applicationInfo.packageName, - clientPkg.applicationInfo.uid); + clientPkg.applicationInfo.uid, "update lib"); } } @@ -4589,6 +4756,24 @@ public class PackageManagerService extends IPackageManager.Stub { } } + // Add the package's KeySets to the global KeySetManager + KeySetManager ksm = mSettings.mKeySetManager; + try { + ksm.addSigningKeySetToPackage(pkg.packageName, pkg.mSigningKeys); + if (pkg.mKeySetMapping != null) { + for (Map.Entry<String, Set<PublicKey>> entry : pkg.mKeySetMapping.entrySet()) { + if (entry.getValue() != null) { + ksm.addDefinedKeySetToPackage(pkg.packageName, + entry.getValue(), entry.getKey()); + } + } + } + } catch (NullPointerException e) { + Slog.e(TAG, "Could not add KeySet to " + pkg.packageName, e); + } catch (IllegalArgumentException e) { + Slog.e(TAG, "Could not add KeySet to malformed package" + pkg.packageName, e); + } + int N = pkg.providers.size(); StringBuilder r = null; int i; @@ -4596,8 +4781,7 @@ public class PackageManagerService extends IPackageManager.Stub { PackageParser.Provider p = pkg.providers.get(i); p.info.processName = fixProcessName(pkg.applicationInfo.processName, p.info.processName, pkg.applicationInfo.uid); - mProvidersByComponent.put(new ComponentName(p.info.packageName, - p.info.name), p); + mProviders.addProvider(p); p.syncable = p.info.isSyncable; if (p.info.authority != null) { String names[] = p.info.authority.split(";"); @@ -4614,8 +4798,8 @@ public class PackageManagerService extends IPackageManager.Stub { p = new PackageParser.Provider(p); p.syncable = false; } - if (!mProviders.containsKey(names[j])) { - mProviders.put(names[j], p); + if (!mProvidersByAuthority.containsKey(names[j])) { + mProvidersByAuthority.put(names[j], p); if (p.info.authority == null) { p.info.authority = names[j]; } else { @@ -4628,7 +4812,7 @@ public class PackageManagerService extends IPackageManager.Stub { + p.info.isSyncable); } } else { - PackageParser.Provider other = mProviders.get(names[j]); + PackageParser.Provider other = mProvidersByAuthority.get(names[j]); Slog.w(TAG, "Skipping provider name " + names[j] + " (in package " + pkg.applicationInfo.packageName + "): name already used by " @@ -4845,6 +5029,30 @@ public class PackageManagerService extends IPackageManager.Stub { return pkg; } + private void setUpCustomResolverActivity(PackageParser.Package pkg) { + synchronized (mPackages) { + mResolverReplaced = true; + // Set up information for custom user intent resolution activity. + mResolveActivity.applicationInfo = pkg.applicationInfo; + mResolveActivity.name = mCustomResolverComponentName.getClassName(); + mResolveActivity.packageName = pkg.applicationInfo.packageName; + mResolveActivity.processName = null; + mResolveActivity.launchMode = ActivityInfo.LAUNCH_MULTIPLE; + mResolveActivity.flags = ActivityInfo.FLAG_EXCLUDE_FROM_RECENTS | + ActivityInfo.FLAG_FINISH_ON_CLOSE_SYSTEM_DIALOGS; + mResolveActivity.theme = 0; + mResolveActivity.exported = true; + mResolveActivity.enabled = true; + mResolveInfo.activityInfo = mResolveActivity; + mResolveInfo.priority = 0; + mResolveInfo.preferredOrder = 0; + mResolveInfo.match = 0; + mResolveComponentName = mCustomResolverComponentName; + Slog.i(TAG, "Replacing default ResolverActivity with custom activity: " + + mResolveComponentName); + } + } + private void setInternalAppNativeLibraryPath(PackageParser.Package pkg, PackageSetting pkgSetting) { final String apkLibPath = getApkName(pkgSetting.codePathString); @@ -4880,14 +5088,14 @@ public class PackageManagerService extends IPackageManager.Stub { return NativeLibraryHelper.copyNativeBinariesIfNeededLI(scanFile, nativeLibraryDir); } - private void killApplication(String pkgName, int appId) { + private void killApplication(String pkgName, int appId, String reason) { // Request the ActivityManager to kill the process(only for existing packages) // so that we do not end up in a confused state while the user is still using the older // version of the application while the new one gets installed. IActivityManager am = ActivityManagerNative.getDefault(); if (am != null) { try { - am.killApplicationWithAppId(pkgName, appId); + am.killApplicationWithAppId(pkgName, appId, reason); } catch (RemoteException e) { } } @@ -4935,8 +5143,7 @@ public class PackageManagerService extends IPackageManager.Stub { int i; for (i=0; i<N; i++) { PackageParser.Provider p = pkg.providers.get(i); - mProvidersByComponent.remove(new ComponentName(p.info.packageName, - p.info.name)); + mProviders.removeProvider(p); if (p.info.authority == null) { /* There was another ContentProvider with this authority when @@ -4947,8 +5154,8 @@ public class PackageManagerService extends IPackageManager.Stub { } String names[] = p.info.authority.split(";"); for (int j = 0; j < names.length; j++) { - if (mProviders.get(names[j]) == p) { - mProviders.remove(names[j]); + if (mProvidersByAuthority.get(names[j]) == p) { + mProvidersByAuthority.remove(names[j]); if (DEBUG_REMOVE) { if (chatty) Log.d(TAG, "Unregistered content provider: " + names[j] @@ -5348,17 +5555,20 @@ public class PackageManagerService extends IPackageManager.Stub { .getDisabledSystemPkgLPr(pkg.packageName); final GrantedPermissions origGp = sysPs.sharedUser != null ? sysPs.sharedUser : sysPs; + if (origGp.grantedPermissions.contains(perm)) { + // If the original was granted this permission, we take + // that grant decision as read and propagate it to the + // update. allowed = true; } else { // The system apk may have been updated with an older // version of the one on the data partition, but which // granted a new system permission that it didn't have // before. In this case we do want to allow the app to - // now get the new permission, because it is allowed by - // the system image. - allowed = false; - if (sysPs.pkg != null) { + // now get the new permission if the new system-partition + // apk is privileged to get it. + if (sysPs.pkg != null && isPrivilegedApp(pkg)) { for (int j=0; j<sysPs.pkg.requestedPermissions.size(); j++) { if (perm.equals( @@ -5370,7 +5580,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } } else { - allowed = true; + allowed = isPrivilegedApp(pkg); } } } @@ -5564,7 +5774,7 @@ public class PackageManagerService extends IPackageManager.Stub { out.print(prefix); out.print( Integer.toHexString(System.identityHashCode(filter.activity))); out.print(' '); - out.print(filter.activity.getComponentShortName()); + filter.activity.printComponentShortName(out); out.print(" filter "); out.println(Integer.toHexString(System.identityHashCode(filter))); } @@ -5763,7 +5973,7 @@ public class PackageManagerService extends IPackageManager.Stub { out.print(prefix); out.print( Integer.toHexString(System.identityHashCode(filter.service))); out.print(' '); - out.print(filter.service.getComponentShortName()); + filter.service.printComponentShortName(out); out.print(" filter "); out.println(Integer.toHexString(System.identityHashCode(filter))); } @@ -5786,6 +5996,195 @@ public class PackageManagerService extends IPackageManager.Stub { private int mFlags; }; + private final class ProviderIntentResolver + extends IntentResolver<PackageParser.ProviderIntentInfo, ResolveInfo> { + public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, + boolean defaultOnly, int userId) { + mFlags = defaultOnly ? PackageManager.MATCH_DEFAULT_ONLY : 0; + return super.queryIntent(intent, resolvedType, defaultOnly, userId); + } + + public List<ResolveInfo> queryIntent(Intent intent, String resolvedType, int flags, + int userId) { + if (!sUserManager.exists(userId)) + return null; + mFlags = flags; + return super.queryIntent(intent, resolvedType, + (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0, userId); + } + + public List<ResolveInfo> queryIntentForPackage(Intent intent, String resolvedType, + int flags, ArrayList<PackageParser.Provider> packageProviders, int userId) { + if (!sUserManager.exists(userId)) + return null; + if (packageProviders == null) { + return null; + } + mFlags = flags; + final boolean defaultOnly = (flags & PackageManager.MATCH_DEFAULT_ONLY) != 0; + final int N = packageProviders.size(); + ArrayList<PackageParser.ProviderIntentInfo[]> listCut = + new ArrayList<PackageParser.ProviderIntentInfo[]>(N); + + ArrayList<PackageParser.ProviderIntentInfo> intentFilters; + for (int i = 0; i < N; ++i) { + intentFilters = packageProviders.get(i).intents; + if (intentFilters != null && intentFilters.size() > 0) { + PackageParser.ProviderIntentInfo[] array = + new PackageParser.ProviderIntentInfo[intentFilters.size()]; + intentFilters.toArray(array); + listCut.add(array); + } + } + return super.queryIntentFromList(intent, resolvedType, defaultOnly, listCut, userId); + } + + public final void addProvider(PackageParser.Provider p) { + mProviders.put(p.getComponentName(), p); + if (DEBUG_SHOW_INFO) { + Log.v(TAG, " " + + (p.info.nonLocalizedLabel != null + ? p.info.nonLocalizedLabel : p.info.name) + ":"); + Log.v(TAG, " Class=" + p.info.name); + } + final int NI = p.intents.size(); + int j; + for (j = 0; j < NI; j++) { + PackageParser.ProviderIntentInfo intent = p.intents.get(j); + if (DEBUG_SHOW_INFO) { + Log.v(TAG, " IntentFilter:"); + intent.dump(new LogPrinter(Log.VERBOSE, TAG), " "); + } + if (!intent.debugCheck()) { + Log.w(TAG, "==> For Provider " + p.info.name); + } + addFilter(intent); + } + } + + public final void removeProvider(PackageParser.Provider p) { + mProviders.remove(p.getComponentName()); + if (DEBUG_SHOW_INFO) { + Log.v(TAG, " " + (p.info.nonLocalizedLabel != null + ? p.info.nonLocalizedLabel : p.info.name) + ":"); + Log.v(TAG, " Class=" + p.info.name); + } + final int NI = p.intents.size(); + int j; + for (j = 0; j < NI; j++) { + PackageParser.ProviderIntentInfo intent = p.intents.get(j); + if (DEBUG_SHOW_INFO) { + Log.v(TAG, " IntentFilter:"); + intent.dump(new LogPrinter(Log.VERBOSE, TAG), " "); + } + removeFilter(intent); + } + } + + @Override + protected boolean allowFilterResult( + PackageParser.ProviderIntentInfo filter, List<ResolveInfo> dest) { + ProviderInfo filterPi = filter.provider.info; + for (int i = dest.size() - 1; i >= 0; i--) { + ProviderInfo destPi = dest.get(i).providerInfo; + if (destPi.name == filterPi.name + && destPi.packageName == filterPi.packageName) { + return false; + } + } + return true; + } + + @Override + protected PackageParser.ProviderIntentInfo[] newArray(int size) { + return new PackageParser.ProviderIntentInfo[size]; + } + + @Override + protected boolean isFilterStopped(PackageParser.ProviderIntentInfo filter, int userId) { + if (!sUserManager.exists(userId)) + return true; + PackageParser.Package p = filter.provider.owner; + if (p != null) { + PackageSetting ps = (PackageSetting) p.mExtras; + if (ps != null) { + // System apps are never considered stopped for purposes of + // filtering, because there may be no way for the user to + // actually re-launch them. + return (ps.pkgFlags & ApplicationInfo.FLAG_SYSTEM) == 0 + && ps.getStopped(userId); + } + } + return false; + } + + @Override + protected boolean isPackageForFilter(String packageName, + PackageParser.ProviderIntentInfo info) { + return packageName.equals(info.provider.owner.packageName); + } + + @Override + protected ResolveInfo newResult(PackageParser.ProviderIntentInfo filter, + int match, int userId) { + if (!sUserManager.exists(userId)) + return null; + final PackageParser.ProviderIntentInfo info = filter; + if (!mSettings.isEnabledLPr(info.provider.info, mFlags, userId)) { + return null; + } + final PackageParser.Provider provider = info.provider; + if (mSafeMode && (provider.info.applicationInfo.flags + & ApplicationInfo.FLAG_SYSTEM) == 0) { + return null; + } + PackageSetting ps = (PackageSetting) provider.owner.mExtras; + if (ps == null) { + return null; + } + ProviderInfo pi = PackageParser.generateProviderInfo(provider, mFlags, + ps.readUserState(userId), userId); + if (pi == null) { + return null; + } + final ResolveInfo res = new ResolveInfo(); + res.providerInfo = pi; + if ((mFlags & PackageManager.GET_RESOLVED_FILTER) != 0) { + res.filter = filter; + } + res.priority = info.getPriority(); + res.preferredOrder = provider.owner.mPreferredOrder; + res.match = match; + res.isDefault = info.hasDefault; + res.labelRes = info.labelRes; + res.nonLocalizedLabel = info.nonLocalizedLabel; + res.icon = info.icon; + res.system = isSystemApp(res.providerInfo.applicationInfo); + return res; + } + + @Override + protected void sortResults(List<ResolveInfo> results) { + Collections.sort(results, mResolvePrioritySorter); + } + + @Override + protected void dumpFilter(PrintWriter out, String prefix, + PackageParser.ProviderIntentInfo filter) { + out.print(prefix); + out.print( + Integer.toHexString(System.identityHashCode(filter.provider))); + out.print(' '); + filter.provider.printComponentShortName(out); + out.print(" filter "); + out.println(Integer.toHexString(System.identityHashCode(filter))); + } + + private final HashMap<ComponentName, PackageParser.Provider> mProviders + = new HashMap<ComponentName, PackageParser.Provider>(); + private int mFlags; + }; + private static final Comparator<ResolveInfo> mResolvePrioritySorter = new Comparator<ResolveInfo>() { public int compare(ResolveInfo r1, ResolveInfo r2) { @@ -5929,10 +6328,11 @@ public class PackageManagerService extends IPackageManager.Stub { } private final class AppDirObserver extends FileObserver { - public AppDirObserver(String path, int mask, boolean isrom) { + public AppDirObserver(String path, int mask, boolean isrom, boolean isPrivileged) { super(path, mask); mRootDir = path; mIsRom = isrom; + mIsPrivileged = isPrivileged; } public void onEvent(int event, String path) { @@ -5993,11 +6393,15 @@ public class PackageManagerService extends IPackageManager.Stub { if ((event&ADD_EVENTS) != 0) { if (p == null) { if (DEBUG_INSTALL) Slog.d(TAG, "New file appeared: " + fullPath); - p = scanPackageLI(fullPath, - (mIsRom ? PackageParser.PARSE_IS_SYSTEM - | PackageParser.PARSE_IS_SYSTEM_DIR: 0) | - PackageParser.PARSE_CHATTY | - PackageParser.PARSE_MUST_BE_APK, + int flags = PackageParser.PARSE_CHATTY | PackageParser.PARSE_MUST_BE_APK; + if (mIsRom) { + flags |= PackageParser.PARSE_IS_SYSTEM + | PackageParser.PARSE_IS_SYSTEM_DIR; + if (mIsPrivileged) { + flags |= PackageParser.PARSE_IS_PRIVILEGED; + } + } + p = scanPackageLI(fullPath, flags, SCAN_MONITOR | SCAN_NO_PATHS | SCAN_UPDATE_TIME, System.currentTimeMillis(), UserHandle.ALL); if (p != null) { @@ -6040,6 +6444,7 @@ public class PackageManagerService extends IPackageManager.Stub { private final String mRootDir; private final boolean mIsRom; + private final boolean mIsPrivileged; } /* Called when a downloaded package installation has been confirmed by the user */ @@ -6107,6 +6512,122 @@ public class PackageManagerService extends IPackageManager.Stub { mHandler.sendMessage(msg); } + private void sendPackageAddedForUser(String packageName, PackageSetting pkgSetting, int userId) { + Bundle extras = new Bundle(1); + extras.putInt(Intent.EXTRA_UID, UserHandle.getUid(userId, pkgSetting.appId)); + + sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, + packageName, extras, null, null, new int[] {userId}); + try { + IActivityManager am = ActivityManagerNative.getDefault(); + final boolean isSystem = + isSystemApp(pkgSetting) || isUpdatedSystemApp(pkgSetting); + if (isSystem && am.isUserRunning(userId, false)) { + // The just-installed/enabled app is bundled on the system, so presumed + // to be able to run automatically without needing an explicit launch. + // Send it a BOOT_COMPLETED if it would ordinarily have gotten one. + Intent bcIntent = new Intent(Intent.ACTION_BOOT_COMPLETED) + .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) + .setPackage(packageName); + am.broadcastIntent(null, bcIntent, null, null, 0, null, null, null, + android.app.AppOpsManager.OP_NONE, false, false, userId); + } + } catch (RemoteException e) { + // shouldn't happen + Slog.w(TAG, "Unable to bootstrap installed package", e); + } + } + + @Override + public boolean setApplicationBlockedSettingAsUser(String packageName, boolean blocked, + int userId) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null); + PackageSetting pkgSetting; + final int uid = Binder.getCallingUid(); + if (UserHandle.getUserId(uid) != userId) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "setApplicationBlockedSetting for user " + userId); + } + + if (blocked && isPackageDeviceAdmin(packageName, userId)) { + Slog.w(TAG, "Not blocking package " + packageName + ": has active device admin"); + return false; + } + + long callingId = Binder.clearCallingIdentity(); + try { + boolean sendAdded = false; + boolean sendRemoved = false; + // writer + synchronized (mPackages) { + pkgSetting = mSettings.mPackages.get(packageName); + if (pkgSetting == null) { + return false; + } + if (pkgSetting.getBlocked(userId) != blocked) { + pkgSetting.setBlocked(blocked, userId); + mSettings.writePackageRestrictionsLPr(userId); + if (blocked) { + sendRemoved = true; + } else { + sendAdded = true; + } + } + } + if (sendAdded) { + sendPackageAddedForUser(packageName, pkgSetting, userId); + return true; + } + if (sendRemoved) { + killApplication(packageName, UserHandle.getUid(userId, pkgSetting.appId), + "blocking pkg"); + sendPackageBlockedForUser(packageName, pkgSetting, userId); + } + } finally { + Binder.restoreCallingIdentity(callingId); + } + return false; + } + + private void sendPackageBlockedForUser(String packageName, PackageSetting pkgSetting, + int userId) { + final PackageRemovedInfo info = new PackageRemovedInfo(); + info.removedPackage = packageName; + info.removedUsers = new int[] {userId}; + info.uid = UserHandle.getUid(userId, pkgSetting.appId); + info.sendBroadcast(false, false, false); + } + + /** + * Returns true if application is not found or there was an error. Otherwise it returns + * the blocked state of the package for the given user. + */ + @Override + public boolean getApplicationBlockedSettingAsUser(String packageName, int userId) { + mContext.enforceCallingOrSelfPermission(android.Manifest.permission.MANAGE_USERS, null); + PackageSetting pkgSetting; + final int uid = Binder.getCallingUid(); + if (UserHandle.getUserId(uid) != userId) { + mContext.enforceCallingPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, + "getApplicationBlocked for user " + userId); + } + long callingId = Binder.clearCallingIdentity(); + try { + // writer + synchronized (mPackages) { + pkgSetting = mSettings.mPackages.get(packageName); + if (pkgSetting == null) { + return true; + } + return pkgSetting.getBlocked(userId); + } + } finally { + Binder.restoreCallingIdentity(callingId); + } + } + /** * @hide */ @@ -6138,33 +6659,14 @@ public class PackageManagerService extends IPackageManager.Stub { } if (!pkgSetting.getInstalled(userId)) { pkgSetting.setInstalled(true, userId); + pkgSetting.setBlocked(false, userId); mSettings.writePackageRestrictionsLPr(userId); - extras.putInt(Intent.EXTRA_UID, UserHandle.getUid(userId, pkgSetting.appId)); sendAdded = true; } } if (sendAdded) { - sendPackageBroadcast(Intent.ACTION_PACKAGE_ADDED, - packageName, extras, null, null, new int[] {userId}); - try { - IActivityManager am = ActivityManagerNative.getDefault(); - final boolean isSystem = - isSystemApp(pkgSetting) || isUpdatedSystemApp(pkgSetting); - if (isSystem && am.isUserRunning(userId, false)) { - // The just-installed/enabled app is bundled on the system, so presumed - // to be able to run automatically without needing an explicit launch. - // Send it a BOOT_COMPLETED if it would ordinarily have gotten one. - Intent bcIntent = new Intent(Intent.ACTION_BOOT_COMPLETED) - .addFlags(Intent.FLAG_INCLUDE_STOPPED_PACKAGES) - .setPackage(packageName); - am.broadcastIntent(null, bcIntent, null, null, 0, null, null, null, - android.app.AppOpsManager.OP_NONE, false, false, userId); - } - } catch (RemoteException e) { - // shouldn't happen - Slog.w(TAG, "Unable to bootstrap installed package", e); - } + sendPackageAddedForUser(packageName, pkgSetting, userId); } } finally { Binder.restoreCallingIdentity(callingId); @@ -6640,31 +7142,20 @@ public class PackageManagerService extends IPackageManager.Stub { if (mounted) { final UserEnvironment userEnv = new UserEnvironment(mStats.userHandle); - final File externalCacheDir = userEnv - .getExternalStorageAppCacheDirectory(mStats.packageName); - final long externalCacheSize = mContainerService - .calculateDirectorySize(externalCacheDir.getPath()); - mStats.externalCacheSize = externalCacheSize; + mStats.externalCacheSize = calculateDirectorySize(mContainerService, + userEnv.buildExternalStorageAppCacheDirs(mStats.packageName)); - final File externalDataDir = userEnv - .getExternalStorageAppDataDirectory(mStats.packageName); - long externalDataSize = mContainerService.calculateDirectorySize(externalDataDir - .getPath()); + mStats.externalDataSize = calculateDirectorySize(mContainerService, + userEnv.buildExternalStorageAppDataDirs(mStats.packageName)); - if (externalCacheDir.getParentFile().equals(externalDataDir)) { - externalDataSize -= externalCacheSize; - } - mStats.externalDataSize = externalDataSize; + // Always subtract cache size, since it's a subdirectory + mStats.externalDataSize -= mStats.externalCacheSize; - final File externalMediaDir = userEnv - .getExternalStorageAppMediaDirectory(mStats.packageName); - mStats.externalMediaSize = mContainerService - .calculateDirectorySize(externalMediaDir.getPath()); + mStats.externalMediaSize = calculateDirectorySize(mContainerService, + userEnv.buildExternalStorageAppMediaDirs(mStats.packageName)); - final File externalObbDir = userEnv - .getExternalStorageAppObbDirectory(mStats.packageName); - mStats.externalObbSize = mContainerService.calculateDirectorySize(externalObbDir - .getPath()); + mStats.externalObbSize = calculateDirectorySize(mContainerService, + userEnv.buildExternalStorageAppObbDirs(mStats.packageName)); } } @@ -6686,6 +7177,24 @@ public class PackageManagerService extends IPackageManager.Stub { } } + private static long calculateDirectorySize(IMediaContainerService mcs, File[] paths) + throws RemoteException { + long result = 0; + for (File path : paths) { + result += mcs.calculateDirectorySize(path.getAbsolutePath()); + } + return result; + } + + private static void clearDirectory(IMediaContainerService mcs, File[] paths) { + for (File path : paths) { + try { + mcs.clearDirectory(path.getAbsolutePath()); + } catch (RemoteException e) { + } + } + } + class InstallParams extends HandlerParams { final IPackageInstallObserver observer; int flags; @@ -8231,6 +8740,9 @@ public class PackageManagerService extends IPackageManager.Stub { boolean updatedSettings = false; parseFlags |= PackageManager.INSTALL_REPLACE_EXISTING | PackageParser.PARSE_IS_SYSTEM; + if ((deletedPackage.applicationInfo.flags&ApplicationInfo.FLAG_PRIVILEGED) != 0) { + parseFlags |= PackageParser.PARSE_IS_PRIVILEGED; + } String packageName = deletedPackage.packageName; res.returnCode = PackageManager.INSTALL_FAILED_REPLACE_COULDNT_DELETE; if (packageName == null) { @@ -8250,7 +8762,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } - killApplication(packageName, oldPkg.applicationInfo.uid); + killApplication(packageName, oldPkg.applicationInfo.uid, "replace sys pkg"); res.removedInfo.uid = oldPkg.applicationInfo.uid; res.removedInfo.removedPackage = packageName; @@ -8543,6 +9055,10 @@ public class PackageManagerService extends IPackageManager.Stub { return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; } + private static boolean isPrivilegedApp(PackageParser.Package pkg) { + return (pkg.applicationInfo.flags & ApplicationInfo.FLAG_PRIVILEGED) != 0; + } + private static boolean isSystemApp(ApplicationInfo info) { return (info.flags & ApplicationInfo.FLAG_SYSTEM) != 0; } @@ -8651,6 +9167,19 @@ public class PackageManagerService extends IPackageManager.Stub { }); } + private boolean isPackageDeviceAdmin(String packageName, int userId) { + IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface( + ServiceManager.getService(Context.DEVICE_POLICY_SERVICE)); + try { + if (dpm != null && (dpm.packageHasActiveAdmins(packageName, userId) + || dpm.isDeviceOwner(packageName))) { + return true; + } + } catch (RemoteException e) { + } + return false; + } + /** * This method is an internal method that could be get invoked either * to delete an installed package or to clean up a failed installation. @@ -8669,15 +9198,9 @@ public class PackageManagerService extends IPackageManager.Stub { final PackageRemovedInfo info = new PackageRemovedInfo(); final boolean res; - IDevicePolicyManager dpm = IDevicePolicyManager.Stub.asInterface( - ServiceManager.getService(Context.DEVICE_POLICY_SERVICE)); - try { - if (dpm != null && (dpm.packageHasActiveAdmins(packageName, userId) - || dpm.isDeviceOwner(packageName))) { - Slog.w(TAG, "Not removing package " + packageName + ": has active device admin"); - return PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER; - } - } catch (RemoteException e) { + if (isPackageDeviceAdmin(packageName, userId)) { + Slog.w(TAG, "Not removing package " + packageName + ": has active device admin"); + return PackageManager.DELETE_FAILED_DEVICE_POLICY_MANAGER; } boolean removedForAllUsers = false; @@ -8847,6 +9370,17 @@ public class PackageManagerService extends IPackageManager.Stub { } } + boolean locationIsPrivileged(File path) { + try { + final String privilegedAppDir = new File(Environment.getRootDirectory(), "priv-app") + .getCanonicalPath(); + return path.getCanonicalPath().startsWith(privilegedAppDir); + } catch (IOException e) { + Slog.e(TAG, "Unable to access code path " + path); + } + return false; + } + /* * Tries to delete system package. */ @@ -8902,9 +9436,12 @@ public class PackageManagerService extends IPackageManager.Stub { } // Install the system package if (DEBUG_REMOVE) Slog.d(TAG, "Re-installing system package: " + disabledPs); + int parseFlags = PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM; + if (locationIsPrivileged(disabledPs.codePath)) { + parseFlags |= PackageParser.PARSE_IS_PRIVILEGED; + } PackageParser.Package newPkg = scanPackageLI(disabledPs.codePath, - PackageParser.PARSE_MUST_BE_APK | PackageParser.PARSE_IS_SYSTEM, - SCAN_MONITOR | SCAN_NO_PATHS, 0, null); + parseFlags, SCAN_MONITOR | SCAN_NO_PATHS, 0, null); if (newPkg == null) { Slog.w(TAG, "Failed to restore system package:" + newPs.name @@ -8993,6 +9530,7 @@ public class PackageManagerService extends IPackageManager.Stub { false, //installed true, //stopped true, //notLaunched + false, //blocked null, null, null); if (!isSystemApp(ps)) { if (ps.isAnyInstalled(sUserManager.getUserIds())) { @@ -9043,7 +9581,9 @@ public class PackageManagerService extends IPackageManager.Stub { removePackageDataLI(ps, null, null, outInfo, flags, writeSettings); return true; } + boolean ret = false; + mSettings.mKeySetManager.removeAppKeySetData(packageName); if (isSystemApp(ps)) { if (DEBUG_REMOVE) Slog.d(TAG, "Removing system package:" + ps.name); // When an updated system application is deleted we delete the existing resources as well and @@ -9053,11 +9593,12 @@ public class PackageManagerService extends IPackageManager.Stub { } else { if (DEBUG_REMOVE) Slog.d(TAG, "Removing non-system package:" + ps.name); // Kill application pre-emptively especially for apps on sd. - killApplication(packageName, ps.appId); + killApplication(packageName, ps.appId, "uninstall pkg"); ret = deleteInstalledPackageLI(ps, deleteCodeAndResources, flags, allUserHandles, perUserInstalled, outInfo, writeSettings); } + return ret; } @@ -9119,25 +9660,13 @@ public class PackageManagerService extends IPackageManager.Stub { } final UserEnvironment userEnv = new UserEnvironment(curUser); - final File externalCacheDir = userEnv - .getExternalStorageAppCacheDirectory(packageName); - try { - conn.mContainerService.clearDirectory(externalCacheDir.toString()); - } catch (RemoteException e) { - } + clearDirectory(conn.mContainerService, + userEnv.buildExternalStorageAppCacheDirs(packageName)); if (allData) { - final File externalDataDir = userEnv - .getExternalStorageAppDataDirectory(packageName); - try { - conn.mContainerService.clearDirectory(externalDataDir.toString()); - } catch (RemoteException e) { - } - final File externalMediaDir = userEnv - .getExternalStorageAppMediaDirectory(packageName); - try { - conn.mContainerService.clearDirectory(externalMediaDir.toString()); - } catch (RemoteException e) { - } + clearDirectory(conn.mContainerService, + userEnv.buildExternalStorageAppDataDirs(packageName)); + clearDirectory(conn.mContainerService, + userEnv.buildExternalStorageAppMediaDirs(packageName)); } } } finally { @@ -9410,9 +9939,14 @@ public class PackageManagerService extends IPackageManager.Stub { } return Build.VERSION_CODES.CUR_DEVELOPMENT; } - + public void addPreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity, int userId) { + addPreferredActivityInternal(filter, match, set, activity, true, userId); + } + + private void addPreferredActivityInternal(IntentFilter filter, int match, + ComponentName[] set, ComponentName activity, boolean always, int userId) { // writer int callingUid = Binder.getCallingUid(); enforceCrossUserPermission(callingUid, userId, true, "add preferred activity"); @@ -9433,7 +9967,7 @@ public class PackageManagerService extends IPackageManager.Stub { Slog.i(TAG, "Adding preferred activity " + activity + " for user " + userId + " :"); filter.dump(new LogPrinter(Log.INFO, TAG), " "); mSettings.editPreferredActivitiesLPw(userId).addFilter( - new PreferredActivity(filter, match, set, activity)); + new PreferredActivity(filter, match, set, activity, always)); mSettings.writePackageRestrictionsLPr(userId); } } @@ -9444,10 +9978,6 @@ public class PackageManagerService extends IPackageManager.Stub { throw new IllegalArgumentException( "replacePreferredActivity expects filter to have only 1 action."); } - if (filter.countCategories() != 1) { - throw new IllegalArgumentException( - "replacePreferredActivity expects filter to have only 1 category."); - } if (filter.countDataAuthorities() != 0 || filter.countDataPaths() != 0 || filter.countDataSchemes() != 0 @@ -9484,8 +10014,11 @@ public class PackageManagerService extends IPackageManager.Stub { removed = new ArrayList<PreferredActivity>(); } removed.add(pa); - Log.i(TAG, "Removing preferred activity " + pa.mPref.mComponent + ":"); - filter.dump(new LogPrinter(Log.INFO, TAG), " "); + if (DEBUG_PREFERRED) { + Slog.i(TAG, "Removing preferred activity " + + pa.mPref.mComponent + ":"); + filter.dump(new LogPrinter(Log.INFO, TAG), " "); + } } } if (removed != null) { @@ -9495,7 +10028,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } } - addPreferredActivity(filter, match, set, activity, callingUserId); + addPreferredActivityInternal(filter, match, set, activity, true, callingUserId); } } @@ -9540,8 +10073,11 @@ public class PackageManagerService extends IPackageManager.Stub { Iterator<PreferredActivity> it = pir.filterIterator(); while (it.hasNext()) { PreferredActivity pa = it.next(); + // Mark entry for removal only if it matches the package name + // and the entry is of type "always". if (packageName == null || - pa.mPref.mComponent.getPackageName().equals(packageName)) { + (pa.mPref.mComponent.getPackageName().equals(packageName) + && pa.mPref.mAlways)) { if (removed == null) { removed = new ArrayList<PreferredActivity>(); } @@ -9585,7 +10121,8 @@ public class PackageManagerService extends IPackageManager.Stub { while (it.hasNext()) { final PreferredActivity pa = it.next(); if (packageName == null - || pa.mPref.mComponent.getPackageName().equals(packageName)) { + || (pa.mPref.mComponent.getPackageName().equals(packageName) + && pa.mPref.mAlways)) { if (outFilters != null) { outFilters.add(new IntentFilter(pa)); } @@ -9601,6 +10138,29 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + public ComponentName getHomeActivities(List<ResolveInfo> allHomeCandidates) { + Intent intent = new Intent(Intent.ACTION_MAIN); + intent.addCategory(Intent.CATEGORY_HOME); + + final int callingUserId = UserHandle.getCallingUserId(); + List<ResolveInfo> list = queryIntentActivities(intent, null, + PackageManager.GET_META_DATA, callingUserId); + ResolveInfo preferred = findPreferredActivity(intent, null, 0, list, 0, + true, false, false, callingUserId); + + allHomeCandidates.clear(); + if (list != null) { + for (ResolveInfo ri : list) { + allHomeCandidates.add(ri); + } + } + return (preferred == null || preferred.activityInfo == null) + ? null + : new ComponentName(preferred.activityInfo.packageName, + preferred.activityInfo.name); + } + + @Override public void setApplicationEnabledSetting(String appPackageName, int newState, int flags, int userId, String callingPackage) { if (!sUserManager.exists(userId)) return; @@ -9854,6 +10414,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } } + sUserManager.systemReady(); } public boolean isSafeMode() { @@ -9900,6 +10461,8 @@ public class PackageManagerService extends IPackageManager.Stub { public static final int DUMP_PREFERRED_XML = 1 << 10; + public static final int DUMP_KEYSETS = 1 << 11; + public static final int OPTION_SHOW_FILTERS = 1 << 0; private int mTypes; @@ -9997,6 +10560,7 @@ public class PackageManagerService extends IPackageManager.Stub { pw.println(" m[essages]: print collected runtime messages"); pw.println(" v[erifiers]: print package verifier info"); pw.println(" <package.name>: info about given package"); + pw.println(" k[eysets]: print known keysets"); return; } else if ("-f".equals(opt)) { dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS); @@ -10012,6 +10576,9 @@ public class PackageManagerService extends IPackageManager.Stub { // Is this a package name? if ("android".equals(cmd) || cmd.contains(".")) { packageName = cmd; + // When dumping a single package, we always dump all of its + // filter information since the amount of data will be reasonable. + dumpState.setOptionEnabled(DumpState.OPTION_SHOW_FILTERS); } else if ("l".equals(cmd) || "libraries".equals(cmd)) { dumpState.setDump(DumpState.DUMP_LIBS); } else if ("f".equals(cmd) || "features".equals(cmd)) { @@ -10038,6 +10605,8 @@ public class PackageManagerService extends IPackageManager.Stub { dumpState.setDump(DumpState.DUMP_MESSAGES); } else if ("v".equals(cmd) || "verifiers".equals(cmd)) { dumpState.setDump(DumpState.DUMP_VERIFIERS); + } else if ("k".equals(cmd) || "keysets".equals(cmd)) { + dumpState.setDump(DumpState.DUMP_KEYSETS); } } @@ -10045,7 +10614,7 @@ public class PackageManagerService extends IPackageManager.Stub { synchronized (mPackages) { if (dumpState.isDumping(DumpState.DUMP_VERIFIERS) && packageName == null) { if (dumpState.onTitlePrinted()) - pw.println(" "); + pw.println(); pw.println("Verifiers:"); pw.print(" Required: "); pw.print(mRequiredVerifierPackage); @@ -10055,16 +10624,20 @@ public class PackageManagerService extends IPackageManager.Stub { } if (dumpState.isDumping(DumpState.DUMP_LIBS) && packageName == null) { - if (dumpState.onTitlePrinted()) - pw.println(" "); - pw.println("Libraries:"); + boolean printedHeader = false; final Iterator<String> it = mSharedLibraries.keySet().iterator(); while (it.hasNext()) { String name = it.next(); + SharedLibraryEntry ent = mSharedLibraries.get(name); + if (!printedHeader) { + if (dumpState.onTitlePrinted()) + pw.println(); + pw.println("Libraries:"); + printedHeader = true; + } pw.print(" "); pw.print(name); pw.print(" -> "); - SharedLibraryEntry ent = mSharedLibraries.get(name); if (ent.path != null) { pw.print("(jar) "); pw.print(ent.path); @@ -10078,7 +10651,7 @@ public class PackageManagerService extends IPackageManager.Stub { if (dumpState.isDumping(DumpState.DUMP_FEATURES) && packageName == null) { if (dumpState.onTitlePrinted()) - pw.println(" "); + pw.println(); pw.println("Features:"); Iterator<String> it = mAvailableFeatures.keySet().iterator(); while (it.hasNext()) { @@ -10104,6 +10677,11 @@ public class PackageManagerService extends IPackageManager.Stub { dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { dumpState.setTitlePrinted(true); } + if (mProviders.dump(pw, dumpState.getTitlePrinted() ? "\nProvider Resolver Table:" + : "Provider Resolver Table:", " ", packageName, + dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { + dumpState.setTitlePrinted(true); + } } if (dumpState.isDumping(DumpState.DUMP_PREFERRED)) { @@ -10114,7 +10692,7 @@ public class PackageManagerService extends IPackageManager.Stub { dumpState.getTitlePrinted() ? "\nPreferred Activities User " + user + ":" : "Preferred Activities User " + user + ":", " ", - packageName, dumpState.isOptionEnabled(DumpState.OPTION_SHOW_FILTERS))) { + packageName, true)) { dumpState.setTitlePrinted(true); } } @@ -10148,28 +10726,29 @@ public class PackageManagerService extends IPackageManager.Stub { if (dumpState.isDumping(DumpState.DUMP_PROVIDERS)) { boolean printedSomething = false; - for (PackageParser.Provider p : mProvidersByComponent.values()) { + for (PackageParser.Provider p : mProviders.mProviders.values()) { if (packageName != null && !packageName.equals(p.info.packageName)) { continue; } if (!printedSomething) { if (dumpState.onTitlePrinted()) - pw.println(" "); + pw.println(); pw.println("Registered ContentProviders:"); printedSomething = true; } - pw.print(" "); pw.print(p.getComponentShortName()); pw.println(":"); + pw.print(" "); p.printComponentShortName(pw); pw.println(":"); pw.print(" "); pw.println(p.toString()); } printedSomething = false; - for (Map.Entry<String, PackageParser.Provider> entry : mProviders.entrySet()) { + for (Map.Entry<String, PackageParser.Provider> entry : + mProvidersByAuthority.entrySet()) { PackageParser.Provider p = entry.getValue(); if (packageName != null && !packageName.equals(p.info.packageName)) { continue; } if (!printedSomething) { if (dumpState.onTitlePrinted()) - pw.println(" "); + pw.println(); pw.println("ContentProvider Authorities:"); printedSomething = true; } @@ -10181,7 +10760,11 @@ public class PackageManagerService extends IPackageManager.Stub { } } } - + + if (dumpState.isDumping(DumpState.DUMP_KEYSETS)) { + mSettings.mKeySetManager.dump(pw, packageName, dumpState); + } + if (dumpState.isDumping(DumpState.DUMP_PACKAGES)) { mSettings.dumpPackagesLPr(pw, packageName, dumpState); } @@ -10192,10 +10775,10 @@ public class PackageManagerService extends IPackageManager.Stub { if (dumpState.isDumping(DumpState.DUMP_MESSAGES) && packageName == null) { if (dumpState.onTitlePrinted()) - pw.println(" "); + pw.println(); mSettings.dumpReadMessagesLPr(pw, dumpState); - pw.println(" "); + pw.println(); pw.println("Package warning messages:"); final File fname = getSettingsProblemFile(); FileInputStream in = null; @@ -10418,8 +11001,8 @@ public class PackageManagerService extends IPackageManager.Stub { } } - private void sendResourcesChangedBroadcast(boolean mediaStatus, ArrayList<String> pkgList, - int uidArr[], IIntentReceiver finishedReceiver) { + private void sendResourcesChangedBroadcast(boolean mediaStatus, boolean replacing, + ArrayList<String> pkgList, int uidArr[], IIntentReceiver finishedReceiver) { int size = pkgList.size(); if (size > 0) { // Send broadcasts here @@ -10429,6 +11012,9 @@ public class PackageManagerService extends IPackageManager.Stub { if (uidArr != null) { extras.putIntArray(Intent.EXTRA_CHANGED_UID_LIST, uidArr); } + if (replacing && !mediaStatus) { + extras.putBoolean(Intent.EXTRA_REPLACING, replacing); + } String action = mediaStatus ? Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE : Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE; sendPackageBroadcast(action, null, extras, null, finishedReceiver, null); @@ -10531,7 +11117,7 @@ public class PackageManagerService extends IPackageManager.Stub { } // Send a broadcast to let everyone know we are done processing if (pkgList.size() > 0) { - sendResourcesChangedBroadcast(true, pkgList, uidArr, null); + sendResourcesChangedBroadcast(true, false, pkgList, uidArr, null); } // Force gc to avoid any stale parser references that we might have. if (doGc) { @@ -10608,7 +11194,8 @@ public class PackageManagerService extends IPackageManager.Stub { // broadcast when packages get disabled, force a gc to clean things up. // and unload all the containers. if (pkgList.size() > 0) { - sendResourcesChangedBroadcast(false, pkgList, uidArr, new IIntentReceiver.Stub() { + sendResourcesChangedBroadcast(false, false, pkgList, uidArr, + new IIntentReceiver.Stub() { public void performReceive(Intent intent, int resultCode, String data, Bundle extras, boolean ordered, boolean sticky, int sendingUser) throws RemoteException { @@ -10728,7 +11315,7 @@ public class PackageManagerService extends IPackageManager.Stub { } if (returnCode == PackageManager.MOVE_SUCCEEDED) { // Send resources unavailable broadcast - sendResourcesChangedBroadcast(false, pkgList, uidArr, null); + sendResourcesChangedBroadcast(false, true, pkgList, uidArr, null); // Update package code and resource paths synchronized (mInstallLock) { synchronized (mPackages) { @@ -10806,7 +11393,7 @@ public class PackageManagerService extends IPackageManager.Stub { } } // Send resources available broadcast - sendResourcesChangedBroadcast(true, pkgList, uidArr, null); + sendResourcesChangedBroadcast(true, false, pkgList, uidArr, null); } } if (returnCode != PackageManager.MOVE_SUCCEEDED) { @@ -10928,42 +11515,9 @@ public class PackageManagerService extends IPackageManager.Stub { } @Override + @Deprecated public boolean isPermissionEnforced(String permission) { - final boolean enforcedDefault = isPermissionEnforcedDefault(permission); - synchronized (mPackages) { - return isPermissionEnforcedLocked(permission, enforcedDefault); - } - } - - /** - * Check if given permission should be enforced by default. Should always be - * called outside of {@link #mPackages} lock. - */ - private boolean isPermissionEnforcedDefault(String permission) { - if (READ_EXTERNAL_STORAGE.equals(permission)) { - return android.provider.Settings.Global.getInt(mContext.getContentResolver(), - android.provider.Settings.Global.READ_EXTERNAL_STORAGE_ENFORCED_DEFAULT, 0) - != 0; - } else { - return true; - } - } - - /** - * Check if user has requested that given permission be enforced, using - * given default if undefined. - */ - private boolean isPermissionEnforcedLocked(String permission, boolean enforcedDefault) { - if (READ_EXTERNAL_STORAGE.equals(permission)) { - if (mSettings.mReadExternalStorageEnforced != null) { - return mSettings.mReadExternalStorageEnforced; - } else { - // User hasn't defined; fall back to secure default - return enforcedDefault; - } - } else { - return true; - } + return true; } public boolean isStorageLow() { diff --git a/services/java/com/android/server/pm/PackageSetting.java b/services/java/com/android/server/pm/PackageSetting.java index f7f0870d5389..b6f9f5b336b0 100644 --- a/services/java/com/android/server/pm/PackageSetting.java +++ b/services/java/com/android/server/pm/PackageSetting.java @@ -52,4 +52,8 @@ final class PackageSetting extends PackageSettingBase { + Integer.toHexString(System.identityHashCode(this)) + " " + name + "/" + appId + "}"; } -}
\ No newline at end of file + + public int[] getGids() { + return sharedUser != null ? sharedUser.gids : gids; + } +} diff --git a/services/java/com/android/server/pm/PackageSettingBase.java b/services/java/com/android/server/pm/PackageSettingBase.java index e64ec6dbf7f3..7747c8f9ecdf 100644 --- a/services/java/com/android/server/pm/PackageSettingBase.java +++ b/services/java/com/android/server/pm/PackageSettingBase.java @@ -65,6 +65,8 @@ class PackageSettingBase extends GrantedPermissions { boolean permissionsFixed; boolean haveGids; + PackageKeySetData keySetData = new PackageKeySetData(); + private static final PackageUserState DEFAULT_USER_STATE = new PackageUserState(); // Whether this package is currently stopped, thus can not be @@ -120,6 +122,9 @@ class PackageSettingBase extends GrantedPermissions { origPackage = base.origPackage; installerPackageName = base.installerPackageName; + + keySetData = new PackageKeySetData(base.keySetData); + } void init(File codePath, File resourcePath, String nativeLibraryPathString, @@ -170,6 +175,7 @@ class PackageSettingBase extends GrantedPermissions { userState.put(base.userState.keyAt(i), base.userState.valueAt(i)); } installStatus = base.installStatus; + keySetData = base.keySetData; } private PackageUserState modifyUserState(int userId) { @@ -254,14 +260,24 @@ class PackageSettingBase extends GrantedPermissions { modifyUserState(userId).notLaunched = stop; } + boolean getBlocked(int userId) { + return readUserState(userId).blocked; + } + + void setBlocked(boolean blocked, int userId) { + modifyUserState(userId).blocked = blocked; + } + void setUserState(int userId, int enabled, boolean installed, boolean stopped, - boolean notLaunched, String lastDisableAppCaller, HashSet<String> enabledComponents, + boolean notLaunched, boolean blocked, + String lastDisableAppCaller, HashSet<String> enabledComponents, HashSet<String> disabledComponents) { PackageUserState state = modifyUserState(userId); state.enabled = enabled; state.installed = installed; state.stopped = stopped; state.notLaunched = notLaunched; + state.blocked = blocked; state.lastDisableAppCaller = lastDisableAppCaller; state.enabledComponents = enabledComponents; state.disabledComponents = disabledComponents; diff --git a/services/java/com/android/server/pm/PreferredActivity.java b/services/java/com/android/server/pm/PreferredActivity.java index c655bb16bfd7..f93ba2fe9c59 100644 --- a/services/java/com/android/server/pm/PreferredActivity.java +++ b/services/java/com/android/server/pm/PreferredActivity.java @@ -33,13 +33,13 @@ class PreferredActivity extends IntentFilter implements PreferredComponent.Callb private static final String TAG = "PreferredActivity"; private static final boolean DEBUG_FILTERS = false; - static final String ATTR_USER_ID = "userId"; final PreferredComponent mPref; - PreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity) { + PreferredActivity(IntentFilter filter, int match, ComponentName[] set, ComponentName activity, + boolean always) { super(filter); - mPref = new PreferredComponent(this, match, set, activity); + mPref = new PreferredComponent(this, match, set, activity, always); } PreferredActivity(XmlPullParser parser) throws XmlPullParserException, IOException { @@ -71,4 +71,10 @@ class PreferredActivity extends IntentFilter implements PreferredComponent.Callb } return true; } + + @Override + public String toString() { + return "PreferredActivity{0x" + Integer.toHexString(System.identityHashCode(this)) + + " " + mPref.mComponent.flattenToShortString() + "}"; + } } diff --git a/services/java/com/android/server/pm/PreferredIntentResolver.java b/services/java/com/android/server/pm/PreferredIntentResolver.java index 7fe6a05aa325..bce24d7b8817 100644 --- a/services/java/com/android/server/pm/PreferredIntentResolver.java +++ b/services/java/com/android/server/pm/PreferredIntentResolver.java @@ -26,10 +26,12 @@ public class PreferredIntentResolver protected PreferredActivity[] newArray(int size) { return new PreferredActivity[size]; } + @Override protected boolean isPackageForFilter(String packageName, PreferredActivity filter) { return packageName.equals(filter.mPref.mComponent.getPackageName()); } + @Override protected void dumpFilter(PrintWriter out, String prefix, PreferredActivity filter) { diff --git a/services/java/com/android/server/pm/Settings.java b/services/java/com/android/server/pm/Settings.java index b0679f227b91..5bc4d055fa40 100644 --- a/services/java/com/android/server/pm/Settings.java +++ b/services/java/com/android/server/pm/Settings.java @@ -22,6 +22,8 @@ import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER; import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_ENABLED; import static android.Manifest.permission.READ_EXTERNAL_STORAGE; +import static android.os.Process.SYSTEM_UID; +import static android.os.Process.PACKAGE_INFO_GID; import android.content.IntentFilter; import android.content.pm.ActivityInfo; @@ -43,6 +45,7 @@ import android.content.Context; import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.ComponentInfo; +import android.content.pm.KeySet; import android.content.pm.PackageCleanItem; import android.content.pm.PackageManager; import android.content.pm.PackageParser; @@ -57,6 +60,7 @@ import android.os.FileUtils; import android.os.Process; import android.os.UserHandle; import android.util.Log; +import android.util.LongSparseArray; import android.util.Slog; import android.util.SparseArray; import android.util.Xml; @@ -67,6 +71,7 @@ import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; +import java.security.PublicKey; import java.text.SimpleDateFormat; import java.util.ArrayList; import java.util.Arrays; @@ -106,6 +111,7 @@ final class Settings { private static final String ATTR_ENABLED = "enabled"; private static final String ATTR_ENABLED_CALLER = "enabledCaller"; private static final String ATTR_STOPPED = "stopped"; + private static final String ATTR_BLOCKED = "blocked"; private static final String ATTR_INSTALLED = "inst"; private final File mSettingsFilename; @@ -113,12 +119,15 @@ final class Settings { private final File mPackageListFilename; private final File mStoppedPackagesFilename; private final File mBackupStoppedPackagesFilename; + final HashMap<String, PackageSetting> mPackages = new HashMap<String, PackageSetting>(); // List of replaced system applications private final HashMap<String, PackageSetting> mDisabledSysPackages = new HashMap<String, PackageSetting>(); + private static int mFirstAvailableUid = 0; + // These are the last platform API version we were using for // the apps installed on internal and external storage. It is // used to grant newer permissions one time during a system upgrade. @@ -177,6 +186,9 @@ final class Settings { private final Context mContext; private final File mSystemDir; + + public final KeySetManager mKeySetManager = new KeySetManager(mPackages); + Settings(Context context) { this(context, Environment.getDataDirectory()); } @@ -192,6 +204,8 @@ final class Settings { mSettingsFilename = new File(mSystemDir, "packages.xml"); mBackupSettingsFilename = new File(mSystemDir, "packages-backup.xml"); mPackageListFilename = new File(mSystemDir, "packages.list"); + FileUtils.setPermissions(mPackageListFilename, 0660, SYSTEM_UID, PACKAGE_INFO_GID); + // Deprecated: Needed for migration mStoppedPackagesFilename = new File(mSystemDir, "packages-stopped.xml"); mBackupStoppedPackagesFilename = new File(mSystemDir, "packages-stopped-backup.xml"); @@ -337,6 +351,19 @@ final class Settings { return null; } + void pruneSharedUsersLPw() { + ArrayList<String> removeStage = new ArrayList<String>(); + for (Map.Entry<String,SharedUserSetting> entry : mSharedUsers.entrySet()) { + final SharedUserSetting sus = entry.getValue(); + if (sus == null || sus.packages.size() == 0) { + removeStage.add(entry.getKey()); + } + } + for (int i = 0; i < removeStage.size(); i++) { + mSharedUsers.remove(removeStage.get(i)); + } + } + // Transfer ownership of permissions from one package to another. void transferPermissionsLPw(String origPkg, String newPkg) { // Transfer ownership of permissions to the new package. @@ -454,6 +481,7 @@ final class Settings { installed, true, // stopped, true, // notLaunched + false, // blocked null, null, null); writePackageRestrictionsLPr(user.id); } @@ -584,7 +612,7 @@ final class Settings { "Package " + p.name + " was user " + p.sharedUser + " but is now " + sharedUser + "; I am not changing its files so it will probably fail!"); - p.sharedUser.packages.remove(p); + p.sharedUser.removePackage(p); } else if (p.appId != sharedUser.userId) { PackageManagerService.reportSettingsProblem(Log.ERROR, "Package " + p.name + " was user id " + p.appId @@ -593,7 +621,7 @@ final class Settings { + "; I am not changing its files so it will probably fail!"); } - sharedUser.packages.add(p); + sharedUser.addPackage(p); p.sharedUser = sharedUser; p.appId = sharedUser.userId; } @@ -653,7 +681,7 @@ final class Settings { if (p != null) { mPackages.remove(name); if (p.sharedUser != null) { - p.sharedUser.packages.remove(p); + p.sharedUser.removePackage(p); if (p.sharedUser.packages.size() == 0) { mSharedUsers.remove(p.sharedUser.name); removeUserIdLPw(p.sharedUser.userId); @@ -671,8 +699,8 @@ final class Settings { final PackageSetting p = mPackages.get(name); if (p != null) { if (p.sharedUser != null) { - p.sharedUser.packages.remove(p); - p.sharedUser.packages.add(newp); + p.sharedUser.removePackage(p); + p.sharedUser.addPackage(newp); } else { replaceUserIdLPw(p.appId, newp); } @@ -729,6 +757,7 @@ final class Settings { } else { mOtherUserIds.remove(uid); } + setFirstAvailableUid(uid+1); } private void replaceUserIdLPw(int uid, Object obj) { @@ -851,6 +880,7 @@ final class Settings { true, // installed false, // stopped false, // notLaunched + false, // blocked null, null, null); } return; @@ -904,6 +934,9 @@ final class Settings { final String stoppedStr = parser.getAttributeValue(null, ATTR_STOPPED); final boolean stopped = stoppedStr == null ? false : Boolean.parseBoolean(stoppedStr); + final String blockedStr = parser.getAttributeValue(null, ATTR_BLOCKED); + final boolean blocked = blockedStr == null + ? false : Boolean.parseBoolean(blockedStr); final String notLaunchedStr = parser.getAttributeValue(null, ATTR_NOT_LAUNCHED); final boolean notLaunched = stoppedStr == null ? false : Boolean.parseBoolean(notLaunchedStr); @@ -927,7 +960,7 @@ final class Settings { } } - ps.setUserState(userId, enabled, installed, stopped, notLaunched, + ps.setUserState(userId, enabled, installed, stopped, notLaunched, blocked, enabledCaller, enabledComponents, disabledComponents); } else if (tagName.equals("preferred-activities")) { readPreferredActivitiesLPw(parser, userId); @@ -1035,6 +1068,7 @@ final class Settings { PackageUserState ustate = pkg.readUserState(userId); if (ustate.stopped || ustate.notLaunched || !ustate.installed || ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT + || ustate.blocked || (ustate.enabledComponents != null && ustate.enabledComponents.size() > 0) || (ustate.disabledComponents != null @@ -1052,6 +1086,9 @@ final class Settings { if (ustate.notLaunched) { serializer.attribute(null, ATTR_NOT_LAUNCHED, "true"); } + if (ustate.blocked) { + serializer.attribute(null, ATTR_BLOCKED, "true"); + } if (ustate.enabled != COMPONENT_ENABLED_STATE_DEFAULT) { serializer.attribute(null, ATTR_ENABLED, Integer.toString(ustate.enabled)); @@ -1331,6 +1368,8 @@ final class Settings { } } + mKeySetManager.writeKeySetManagerLPr(serializer); + serializer.endTag(null, "packages"); serializer.endDocument(); @@ -1348,22 +1387,29 @@ final class Settings { -1, -1); // Write package list file now, use a JournaledFile. - // - File tempFile = new File(mPackageListFilename.toString() + ".tmp"); + File tempFile = new File(mPackageListFilename.getAbsolutePath() + ".tmp"); JournaledFile journal = new JournaledFile(mPackageListFilename, tempFile); - fstr = new FileOutputStream(journal.chooseForWrite()); + final File writeTarget = journal.chooseForWrite(); + fstr = new FileOutputStream(writeTarget); str = new BufferedOutputStream(fstr); try { + FileUtils.setPermissions(fstr.getFD(), 0660, SYSTEM_UID, PACKAGE_INFO_GID); + StringBuilder sb = new StringBuilder(); for (final PackageSetting pkg : mPackages.values()) { - ApplicationInfo ai = pkg.pkg.applicationInfo; - String dataPath = ai.dataDir; - boolean isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + if (pkg.pkg == null || pkg.pkg.applicationInfo == null) { + Slog.w(TAG, "Skipping " + pkg + " due to missing metadata"); + continue; + } + + final ApplicationInfo ai = pkg.pkg.applicationInfo; + final String dataPath = ai.dataDir; + final boolean isDebug = (ai.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0; + final int[] gids = pkg.getGids(); - // Avoid any application that has a space in its path - // or that is handled by the system. - if (dataPath.indexOf(" ") >= 0 || ai.uid < Process.FIRST_APPLICATION_UID) + // Avoid any application that has a space in its path. + if (dataPath.indexOf(" ") >= 0) continue; // we store on each line the following information for now: @@ -1373,12 +1419,14 @@ final class Settings { // debugFlag - 0 or 1 if the package is debuggable. // dataPath - path to package's data path // seinfo - seinfo label for the app (assigned at install time) + // gids - supplementary gids this app launches with // // NOTE: We prefer not to expose all ApplicationInfo flags for now. // // DO NOT MODIFY THIS FORMAT UNLESS YOU CAN ALSO MODIFY ITS USERS // FROM NATIVE CODE. AT THE MOMENT, LOOK AT THE FOLLOWING SOURCES: // system/core/run-as/run-as.c + // system/core/sdcard/sdcard.c // sb.setLength(0); sb.append(ai.packageName); @@ -1388,6 +1436,16 @@ final class Settings { sb.append(dataPath); sb.append(" "); sb.append(ai.seinfo); + sb.append(" "); + if (gids != null && gids.length > 0) { + sb.append(gids[0]); + for (int i = 1; i < gids.length; i++) { + sb.append(","); + sb.append(gids[i]); + } + } else { + sb.append("none"); + } sb.append("\n"); str.write(sb.toString().getBytes()); } @@ -1396,15 +1454,11 @@ final class Settings { str.close(); journal.commit(); } catch (Exception e) { + Log.wtf(TAG, "Failed to write packages.list", e); IoUtils.closeQuietly(str); journal.rollback(); } - FileUtils.setPermissions(mPackageListFilename.toString(), - FileUtils.S_IRUSR|FileUtils.S_IWUSR - |FileUtils.S_IRGRP|FileUtils.S_IWGRP, - -1, -1); - writeAllUsersPackageRestrictionsLPr(); return; @@ -1521,9 +1575,31 @@ final class Settings { serializer.endTag(null, "perms"); } + writeSigningKeySetsLPr(serializer, pkg.keySetData); + writeKeySetAliasesLPr(serializer, pkg.keySetData); + serializer.endTag(null, "package"); } + void writeSigningKeySetsLPr(XmlSerializer serializer, + PackageKeySetData data) throws IOException { + for (long id : data.getSigningKeySets()) { + serializer.startTag(null, "signing-keyset"); + serializer.attribute(null, "identifier", Long.toString(id)); + serializer.endTag(null, "signing-keyset"); + } + } + + void writeKeySetAliasesLPr(XmlSerializer serializer, + PackageKeySetData data) throws IOException { + for (Map.Entry<String, Long> e: data.getAliases().entrySet()) { + serializer.startTag(null, "defined-keyset"); + serializer.attribute(null, "alias", e.getKey()); + serializer.attribute(null, "identifier", Long.toString(e.getValue())); + serializer.endTag(null, "defined-keyset"); + } + } + void writePermissionLPr(XmlSerializer serializer, BasePermission bp) throws XmlPullParserException, java.io.IOException { if (bp.type != BasePermission.TYPE_BUILTIN && bp.sourcePackage != null) { @@ -1698,6 +1774,8 @@ final class Settings { } else if (TAG_READ_EXTERNAL_STORAGE.equals(tagName)) { final String enforcement = parser.getAttributeValue(null, ATTR_ENFORCEMENT); mReadExternalStorageEnforced = "1".equals(enforcement); + } else if (tagName.equals("keyset-settings")) { + mKeySetManager.readKeySetsLPw(parser); } else { Slog.w(PackageManagerService.TAG, "Unknown element under <packages>: " + parser.getName()); @@ -1785,6 +1863,20 @@ final class Settings { } void readDefaultPreferredAppsLPw(PackageManagerService service, int userId) { + // First pull data from any pre-installed apps. + for (PackageSetting ps : mPackages.values()) { + if ((ps.pkgFlags&ApplicationInfo.FLAG_SYSTEM) != 0 && ps.pkg != null + && ps.pkg.preferredActivityFilters != null) { + ArrayList<PackageParser.ActivityIntentInfo> intents + = ps.pkg.preferredActivityFilters; + for (int i=0; i<intents.size(); i++) { + PackageParser.ActivityIntentInfo aii = intents.get(i); + applyDefaultPreferredActivityLPw(service, aii, new ComponentName( + ps.name, aii.activity.className), userId); + } + } + } + // Read preferred apps from .../etc/preferred-apps directory. File preferredDir = new File(Environment.getRootDirectory(), "etc/preferred-apps"); if (!preferredDir.exists() || !preferredDir.isDirectory()) { @@ -1844,6 +1936,166 @@ final class Settings { } } + private void applyDefaultPreferredActivityLPw(PackageManagerService service, + IntentFilter tmpPa, ComponentName cn, int userId) { + // The initial preferences only specify the target activity + // component and intent-filter, not the set of matches. So we + // now need to query for the matches to build the correct + // preferred activity entry. + if (PackageManagerService.DEBUG_PREFERRED) { + Log.d(TAG, "Processing preferred:"); + tmpPa.dump(new LogPrinter(Log.DEBUG, TAG), " "); + } + Intent intent = new Intent(); + int flags = 0; + intent.setAction(tmpPa.getAction(0)); + for (int i=0; i<tmpPa.countCategories(); i++) { + String cat = tmpPa.getCategory(i); + if (cat.equals(Intent.CATEGORY_DEFAULT)) { + flags |= PackageManager.MATCH_DEFAULT_ONLY; + } else { + intent.addCategory(cat); + } + } + + boolean doNonData = true; + + for (int ischeme=0; ischeme<tmpPa.countDataSchemes(); ischeme++) { + boolean doScheme = true; + String scheme = tmpPa.getDataScheme(ischeme); + for (int issp=0; issp<tmpPa.countDataSchemeSpecificParts(); issp++) { + Uri.Builder builder = new Uri.Builder(); + builder.scheme(scheme); + PatternMatcher ssp = tmpPa.getDataSchemeSpecificPart(issp); + builder.opaquePart(ssp.getPath()); + Intent finalIntent = new Intent(intent); + finalIntent.setData(builder.build()); + applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn, + scheme, ssp, null, null, null, userId); + doScheme = false; + } + for (int iauth=0; iauth<tmpPa.countDataAuthorities(); iauth++) { + boolean doAuth = true; + IntentFilter.AuthorityEntry auth = tmpPa.getDataAuthority(iauth); + for (int ipath=0; ipath<tmpPa.countDataPaths(); ipath++) { + Uri.Builder builder = new Uri.Builder(); + builder.scheme(scheme); + if (auth.getHost() != null) { + builder.authority(auth.getHost()); + } + PatternMatcher path = tmpPa.getDataPath(ipath); + builder.path(path.getPath()); + Intent finalIntent = new Intent(intent); + finalIntent.setData(builder.build()); + applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn, + scheme, null, auth, path, null, userId); + doAuth = doScheme = false; + } + if (doAuth) { + Uri.Builder builder = new Uri.Builder(); + builder.scheme(scheme); + if (auth.getHost() != null) { + builder.authority(auth.getHost()); + } + Intent finalIntent = new Intent(intent); + finalIntent.setData(builder.build()); + applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn, + scheme, null, auth, null, null, userId); + doScheme = false; + } + } + if (doScheme) { + Uri.Builder builder = new Uri.Builder(); + builder.scheme(scheme); + Intent finalIntent = new Intent(intent); + finalIntent.setData(builder.build()); + applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn, + scheme, null, null, null, null, userId); + } + doNonData = false; + } + + for (int idata=0; idata<tmpPa.countDataTypes(); idata++) { + Intent finalIntent = new Intent(intent); + String mimeType = tmpPa.getDataType(idata); + finalIntent.setType(mimeType); + applyDefaultPreferredActivityLPw(service, finalIntent, flags, cn, + null, null, null, null, mimeType, userId); + doNonData = false; + } + + if (doNonData) { + applyDefaultPreferredActivityLPw(service, intent, flags, cn, + null, null, null, null, null, userId); + } + } + + private void applyDefaultPreferredActivityLPw(PackageManagerService service, + Intent intent, int flags, ComponentName cn, String scheme, PatternMatcher ssp, + IntentFilter.AuthorityEntry auth, PatternMatcher path, String mimeType, + int userId) { + List<ResolveInfo> ri = service.mActivities.queryIntent(intent, + intent.getType(), flags, 0); + if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Queried " + intent + + " results: " + ri); + int match = 0; + if (ri != null && ri.size() > 1) { + boolean haveAct = false; + boolean haveNonSys = false; + ComponentName[] set = new ComponentName[ri.size()]; + for (int i=0; i<ri.size(); i++) { + ActivityInfo ai = ri.get(i).activityInfo; + set[i] = new ComponentName(ai.packageName, ai.name); + if ((ai.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) { + // If any of the matches are not system apps, then + // there is a third party app that is now an option... + // so don't set a default since we don't want to hide it. + if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result " + + ai.packageName + "/" + ai.name + ": non-system!"); + haveNonSys = true; + break; + } else if (cn.getPackageName().equals(ai.packageName) + && cn.getClassName().equals(ai.name)) { + if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result " + + ai.packageName + "/" + ai.name + ": default!"); + haveAct = true; + match = ri.get(i).match; + } else { + if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result " + + ai.packageName + "/" + ai.name + ": skipped"); + } + } + if (haveAct && !haveNonSys) { + IntentFilter filter = new IntentFilter(); + if (intent.getAction() != null) { + filter.addAction(intent.getAction()); + } + for (String cat : intent.getCategories()) { + filter.addCategory(cat); + } + if ((flags&PackageManager.MATCH_DEFAULT_ONLY) != 0) { + filter.addCategory(Intent.CATEGORY_DEFAULT); + } + if (scheme != null) { + filter.addDataScheme(scheme); + } + if (ssp != null) { + filter.addDataSchemeSpecificPart(ssp.getPath(), ssp.getType()); + } + if (auth != null) { + filter.addDataAuthority(auth); + } + if (path != null) { + filter.addDataPath(path); + } + PreferredActivity pa = new PreferredActivity(filter, match, set, cn, true); + editPreferredActivitiesLPw(userId).addFilter(pa); + } else if (!haveNonSys) { + Slog.w(TAG, "No component found for default preferred activity " + cn); + } + } + } + private void readDefaultPreferredActivitiesLPw(PackageManagerService service, XmlPullParser parser, int userId) throws XmlPullParserException, IOException { @@ -1859,83 +2111,8 @@ final class Settings { if (tagName.equals(TAG_ITEM)) { PreferredActivity tmpPa = new PreferredActivity(parser); if (tmpPa.mPref.getParseError() == null) { - // The initial preferences only specify the target activity - // component and intent-filter, not the set of matches. So we - // now need to query for the matches to build the correct - // preferred activity entry. - if (PackageManagerService.DEBUG_PREFERRED) { - Log.d(TAG, "Processing preferred:"); - tmpPa.dump(new LogPrinter(Log.DEBUG, TAG), " "); - } - final ComponentName cn = tmpPa.mPref.mComponent; - Intent intent = new Intent(); - int flags = 0; - intent.setAction(tmpPa.getAction(0)); - for (int i=0; i<tmpPa.countCategories(); i++) { - String cat = tmpPa.getCategory(i); - if (cat.equals(Intent.CATEGORY_DEFAULT)) { - flags |= PackageManager.MATCH_DEFAULT_ONLY; - } else { - intent.addCategory(cat); - } - } - if (tmpPa.countDataSchemes() > 0) { - Uri.Builder builder = new Uri.Builder(); - builder.scheme(tmpPa.getDataScheme(0)); - if (tmpPa.countDataAuthorities() > 0) { - IntentFilter.AuthorityEntry auth = tmpPa.getDataAuthority(0); - if (auth.getHost() != null) { - builder.authority(auth.getHost()); - } - } - if (tmpPa.countDataPaths() > 0) { - PatternMatcher path = tmpPa.getDataPath(0); - builder.path(path.getPath()); - } - intent.setData(builder.build()); - } else if (tmpPa.countDataTypes() > 0) { - intent.setType(tmpPa.getDataType(0)); - } - List<ResolveInfo> ri = service.mActivities.queryIntent(intent, - intent.getType(), flags, 0); - if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Queried " + intent - + " results: " + ri); - int match = 0; - if (ri != null && ri.size() > 1) { - boolean haveAct = false; - boolean haveNonSys = false; - ComponentName[] set = new ComponentName[ri.size()]; - for (int i=0; i<ri.size(); i++) { - ActivityInfo ai = ri.get(i).activityInfo; - set[i] = new ComponentName(ai.packageName, ai.name); - if ((ai.applicationInfo.flags&ApplicationInfo.FLAG_SYSTEM) == 0) { - // If any of the matches are not system apps, then - // there is a third party app that is now an option... - // so don't set a default since we don't want to hide it. - if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result " - + ai.packageName + "/" + ai.name + ": non-system!"); - haveNonSys = true; - break; - } else if (cn.getPackageName().equals(ai.packageName) - && cn.getClassName().equals(ai.name)) { - if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result " - + ai.packageName + "/" + ai.name + ": default!"); - haveAct = true; - match = ri.get(i).match; - } else { - if (PackageManagerService.DEBUG_PREFERRED) Log.d(TAG, "Result " - + ai.packageName + "/" + ai.name + ": skipped"); - } - } - if (haveAct && !haveNonSys) { - PreferredActivity pa = new PreferredActivity(tmpPa, match, set, - tmpPa.mPref.mComponent); - editPreferredActivitiesLPw(userId).addFilter(pa); - } else if (!haveNonSys) { - Slog.w(TAG, "No component found for default preferred activity " - + tmpPa.mPref.mComponent); - } - } + applyDefaultPreferredActivityLPw(service, tmpPa, tmpPa.mPref.mComponent, + userId); } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Error in package manager settings: <preferred-activity> " @@ -2293,12 +2470,23 @@ final class Settings { } else if (tagName.equals("perms")) { readGrantedPermissionsLPw(parser, packageSetting.grantedPermissions); packageSetting.permissionsFixed = true; + } else if (tagName.equals("signing-keyset")) { + long id = Long.parseLong(parser.getAttributeValue(null, "identifier")); + packageSetting.keySetData.addSigningKeySet(id); + if (false) Slog.d(TAG, "Adding signing keyset " + Long.toString(id) + + " to " + name); + } else if (tagName.equals("defined-keyset")) { + long id = Long.parseLong(parser.getAttributeValue(null, "identifier")); + String alias = parser.getAttributeValue(null, "alias"); + packageSetting.keySetData.addDefinedKeySet(id, alias); } else { PackageManagerService.reportSettingsProblem(Log.WARN, "Unknown element under <package>: " + parser.getName()); XmlUtils.skipCurrentTag(parser); } } + + } else { XmlUtils.skipCurrentTag(parser); } @@ -2478,11 +2666,18 @@ final class Settings { file.delete(); } + // This should be called (at least) whenever an application is removed + private void setFirstAvailableUid(int uid) { + if (uid > mFirstAvailableUid) { + mFirstAvailableUid = uid; + } + } + // Returns -1 if we could not find an available UserId to assign private int newUserIdLPw(Object obj) { // Let's be stupidly inefficient for now... final int N = mUserIds.size(); - for (int i = 0; i < N; i++) { + for (int i = mFirstAvailableUid; i < N; i++) { if (mUserIds.get(i) == null) { mUserIds.set(i, obj); return Process.FIRST_APPLICATION_UID + i; @@ -2660,6 +2855,7 @@ final class Settings { ApplicationInfo.FLAG_RESTORE_ANY_VERSION, "RESTORE_ANY_VERSION", ApplicationInfo.FLAG_EXTERNAL_STORAGE, "EXTERNAL_STORAGE", ApplicationInfo.FLAG_LARGE_HEAP, "LARGE_HEAP", + ApplicationInfo.FLAG_PRIVILEGED, "PRIVILEGED", ApplicationInfo.FLAG_FORWARD_LOCK, "FORWARD_LOCK", ApplicationInfo.FLAG_CANT_SAVE_STATE, "CANT_SAVE_STATE", }; @@ -2791,6 +2987,8 @@ final class Settings { pw.print(prefix); pw.print(" User "); pw.print(user.id); pw.print(": "); pw.print(" installed="); pw.print(ps.getInstalled(user.id)); + pw.print(" blocked="); + pw.print(ps.getBlocked(user.id)); pw.print(" stopped="); pw.print(ps.getStopped(user.id)); pw.print(" notLaunched="); @@ -2842,7 +3040,7 @@ final class Settings { if (!printedSomething) { if (dumpState.onTitlePrinted()) - pw.println(" "); + pw.println(); pw.println("Packages:"); printedSomething = true; } @@ -2858,7 +3056,7 @@ final class Settings { } if (!printedSomething) { if (dumpState.onTitlePrinted()) - pw.println(" "); + pw.println(); pw.println("Renamed packages:"); printedSomething = true; } @@ -2878,7 +3076,7 @@ final class Settings { } if (!printedSomething) { if (dumpState.onTitlePrinted()) - pw.println(" "); + pw.println(); pw.println("Hidden system packages:"); printedSomething = true; } @@ -2895,7 +3093,7 @@ final class Settings { } if (!printedSomething) { if (dumpState.onTitlePrinted()) - pw.println(" "); + pw.println(); pw.println("Permissions:"); printedSomething = true; } @@ -2929,7 +3127,7 @@ final class Settings { } if (!printedSomething) { if (dumpState.onTitlePrinted()) - pw.println(" "); + pw.println(); pw.println("Shared users:"); printedSomething = true; } diff --git a/services/java/com/android/server/pm/SharedUserSetting.java b/services/java/com/android/server/pm/SharedUserSetting.java index 76826eab2f07..ca1eeea69745 100644 --- a/services/java/com/android/server/pm/SharedUserSetting.java +++ b/services/java/com/android/server/pm/SharedUserSetting.java @@ -26,12 +26,16 @@ final class SharedUserSetting extends GrantedPermissions { int userId; + // flags that are associated with this uid, regardless of any package flags + int uidFlags; + final HashSet<PackageSetting> packages = new HashSet<PackageSetting>(); final PackageSignatures signatures = new PackageSignatures(); SharedUserSetting(String _name, int _pkgFlags) { super(_pkgFlags); + uidFlags = _pkgFlags; name = _name; } @@ -40,4 +44,23 @@ final class SharedUserSetting extends GrantedPermissions { return "SharedUserSetting{" + Integer.toHexString(System.identityHashCode(this)) + " " + name + "/" + userId + "}"; } + + void removePackage(PackageSetting packageSetting) { + if (packages.remove(packageSetting)) { + // recalculate the pkgFlags for this shared user if needed + if ((this.pkgFlags & packageSetting.pkgFlags) != 0) { + int aggregatedFlags = uidFlags; + for (PackageSetting ps : packages) { + aggregatedFlags |= ps.pkgFlags; + } + setFlags(aggregatedFlags); + } + } + } + + void addPackage(PackageSetting packageSetting) { + if (packages.add(packageSetting)) { + setFlags(this.pkgFlags | packageSetting.pkgFlags); + } + } } diff --git a/services/java/com/android/server/pm/UserManagerService.java b/services/java/com/android/server/pm/UserManagerService.java index 1323c9326bf8..c33134a24d19 100644 --- a/services/java/com/android/server/pm/UserManagerService.java +++ b/services/java/com/android/server/pm/UserManagerService.java @@ -21,11 +21,12 @@ import static android.text.format.DateUtils.MINUTE_IN_MILLIS; import android.app.Activity; import android.app.ActivityManager; import android.app.ActivityManagerNative; +import android.app.ActivityThread; import android.app.IStopUserCallback; import android.content.BroadcastReceiver; import android.content.Context; import android.content.Intent; -import android.content.RestrictionEntry; +import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.pm.UserInfo; @@ -42,12 +43,14 @@ import android.os.RemoteException; import android.os.UserHandle; import android.os.UserManager; import android.util.AtomicFile; +import android.util.Log; import android.util.Slog; import android.util.SparseArray; import android.util.SparseBooleanArray; import android.util.TimeUtils; import android.util.Xml; +import com.android.internal.content.PackageMonitor; import com.android.internal.util.ArrayUtils; import com.android.internal.util.FastXmlSerializer; @@ -63,6 +66,9 @@ import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.PrintWriter; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.security.SecureRandom; import java.util.ArrayList; import java.util.List; @@ -78,6 +84,10 @@ public class UserManagerService extends IUserManager.Stub { private static final String ATTR_ID = "id"; private static final String ATTR_CREATION_TIME = "created"; private static final String ATTR_LAST_LOGGED_IN_TIME = "lastLoggedIn"; + private static final String ATTR_SALT = "salt"; + private static final String ATTR_PIN_HASH = "pinHash"; + private static final String ATTR_FAILED_ATTEMPTS = "failedAttempts"; + private static final String ATTR_LAST_RETRY_MS = "lastAttemptMs"; private static final String ATTR_SERIAL_NO = "serialNumber"; private static final String ATTR_NEXT_SERIAL_NO = "nextSerialNumber"; private static final String ATTR_PARTIAL = "partial"; @@ -100,13 +110,21 @@ public class UserManagerService extends IUserManager.Stub { private static final String USER_PHOTO_FILENAME = "photo.png"; private static final String RESTRICTIONS_FILE_PREFIX = "res_"; + private static final String XML_SUFFIX = ".xml"; private static final int MIN_USER_ID = 10; - private static final int USER_VERSION = 2; + private static final int USER_VERSION = 4; private static final long EPOCH_PLUS_30_YEARS = 30L * 365 * 24 * 60 * 60 * 1000L; // ms + // Number of attempts before jumping to the next BACKOFF_TIMES slot + private static final int BACKOFF_INC_INTERVAL = 5; + + // Amount of time to force the user to wait before entering the PIN again, after failing + // BACKOFF_INC_INTERVAL times. + private static final int[] BACKOFF_TIMES = { 0, 30*1000, 60*1000, 5*60*1000, 30*60*1000 }; + private final Context mContext; private final PackageManagerService mPm; private final Object mInstallLock; @@ -121,6 +139,16 @@ public class UserManagerService extends IUserManager.Stub { private final SparseArray<UserInfo> mUsers = new SparseArray<UserInfo>(); private final SparseArray<Bundle> mUserRestrictions = new SparseArray<Bundle>(); + class RestrictionsPinState { + long salt; + String pinHash; + int failedAttempts; + long lastAttemptTime; + } + + private final SparseArray<RestrictionsPinState> mRestrictionsPinStates = + new SparseArray<RestrictionsPinState>(); + /** * Set of user IDs being actively removed. Removed IDs linger in this set * for several seconds to work around a VFS caching issue. @@ -204,6 +232,13 @@ public class UserManagerService extends IUserManager.Stub { } } + void systemReady() { + final Context context = ActivityThread.systemMain().getSystemContext(); + mUserPackageMonitor.register(context, + null, UserHandle.ALL, false); + userForeground(UserHandle.USER_OWNER); + } + @Override public List<UserInfo> getUsers(boolean excludeDying) { checkManageUsersPermission("query users"); @@ -379,8 +414,10 @@ public class UserManagerService extends IUserManager.Stub { @Override public void setUserRestrictions(Bundle restrictions, int userId) { checkManageUsersPermission("setUserRestrictions"); + if (restrictions == null) return; synchronized (mPackagesLock) { + mUserRestrictions.get(userId).clear(); mUserRestrictions.get(userId).putAll(restrictions); writeUserLocked(mUsers.get(userId)); } @@ -452,12 +489,6 @@ public class UserManagerService extends IUserManager.Stub { return mUserIds; } - private void readUserList() { - synchronized (mPackagesLock) { - readUserListLocked(); - } - } - private void readUserListLocked() { mGuestEnabled = false; if (!mUserListFile.exists()) { @@ -511,7 +542,7 @@ public class UserManagerService extends IUserManager.Stub { } } updateUserIdsLocked(); - upgradeIfNecessary(); + upgradeIfNecessaryLocked(); } catch (IOException ioe) { fallbackToSingleUserLocked(); } catch (XmlPullParserException pe) { @@ -529,7 +560,7 @@ public class UserManagerService extends IUserManager.Stub { /** * Upgrade steps between versions, either for fixing bugs or changing the data format. */ - private void upgradeIfNecessary() { + private void upgradeIfNecessaryLocked() { int userVersion = mUserVersion; if (userVersion < 1) { // Assign a proper name for the owner, if not initialized correctly before @@ -551,6 +582,11 @@ public class UserManagerService extends IUserManager.Stub { userVersion = 2; } + + if (userVersion < 4) { + userVersion = 4; + } + if (userVersion < USER_VERSION) { Slog.w(LOG_TAG, "User version " + mUserVersion + " didn't upgrade as expected to " + USER_VERSION); @@ -567,6 +603,7 @@ public class UserManagerService extends IUserManager.Stub { UserInfo.FLAG_ADMIN | UserInfo.FLAG_PRIMARY | UserInfo.FLAG_INITIALIZED); mUsers.put(0, primary); mNextSerialNumber = MIN_USER_ID; + mUserVersion = USER_VERSION; Bundle restrictions = new Bundle(); mUserRestrictions.append(UserHandle.USER_OWNER, restrictions); @@ -586,7 +623,7 @@ public class UserManagerService extends IUserManager.Stub { */ private void writeUserLocked(UserInfo userInfo) { FileOutputStream fos = null; - AtomicFile userFile = new AtomicFile(new File(mUsersDir, userInfo.id + ".xml")); + AtomicFile userFile = new AtomicFile(new File(mUsersDir, userInfo.id + XML_SUFFIX)); try { fos = userFile.startWrite(); final BufferedOutputStream bos = new BufferedOutputStream(fos); @@ -604,6 +641,21 @@ public class UserManagerService extends IUserManager.Stub { serializer.attribute(null, ATTR_CREATION_TIME, Long.toString(userInfo.creationTime)); serializer.attribute(null, ATTR_LAST_LOGGED_IN_TIME, Long.toString(userInfo.lastLoggedInTime)); + RestrictionsPinState pinState = mRestrictionsPinStates.get(userInfo.id); + if (pinState != null) { + if (pinState.salt != 0) { + serializer.attribute(null, ATTR_SALT, Long.toString(pinState.salt)); + } + if (pinState.pinHash != null) { + serializer.attribute(null, ATTR_PIN_HASH, pinState.pinHash); + } + if (pinState.failedAttempts != 0) { + serializer.attribute(null, ATTR_FAILED_ATTEMPTS, + Integer.toString(pinState.failedAttempts)); + serializer.attribute(null, ATTR_LAST_RETRY_MS, + Long.toString(pinState.lastAttemptTime)); + } + } if (userInfo.iconPath != null) { serializer.attribute(null, ATTR_ICON_PATH, userInfo.iconPath); } @@ -690,13 +742,17 @@ public class UserManagerService extends IUserManager.Stub { String iconPath = null; long creationTime = 0L; long lastLoggedInTime = 0L; + long salt = 0L; + String pinHash = null; + int failedAttempts = 0; + long lastAttemptTime = 0L; boolean partial = false; Bundle restrictions = new Bundle(); FileInputStream fis = null; try { AtomicFile userFile = - new AtomicFile(new File(mUsersDir, Integer.toString(id) + ".xml")); + new AtomicFile(new File(mUsersDir, Integer.toString(id) + XML_SUFFIX)); fis = userFile.openRead(); XmlPullParser parser = Xml.newPullParser(); parser.setInput(fis, null); @@ -722,6 +778,10 @@ public class UserManagerService extends IUserManager.Stub { iconPath = parser.getAttributeValue(null, ATTR_ICON_PATH); creationTime = readLongAttribute(parser, ATTR_CREATION_TIME, 0); lastLoggedInTime = readLongAttribute(parser, ATTR_LAST_LOGGED_IN_TIME, 0); + salt = readLongAttribute(parser, ATTR_SALT, 0L); + pinHash = parser.getAttributeValue(null, ATTR_PIN_HASH); + failedAttempts = readIntAttribute(parser, ATTR_FAILED_ATTEMPTS, 0); + lastAttemptTime = readLongAttribute(parser, ATTR_LAST_RETRY_MS, 0L); String valueString = parser.getAttributeValue(null, ATTR_PARTIAL); if ("true".equals(valueString)) { partial = true; @@ -761,6 +821,17 @@ public class UserManagerService extends IUserManager.Stub { userInfo.lastLoggedInTime = lastLoggedInTime; userInfo.partial = partial; mUserRestrictions.append(id, restrictions); + if (salt != 0L) { + RestrictionsPinState pinState = mRestrictionsPinStates.get(id); + if (pinState == null) { + pinState = new RestrictionsPinState(); + mRestrictionsPinStates.put(id, pinState); + } + pinState.salt = salt; + pinState.pinHash = pinHash; + pinState.failedAttempts = failedAttempts; + pinState.lastAttemptTime = lastAttemptTime; + } return userInfo; } catch (IOException ioe) { @@ -812,6 +883,57 @@ public class UserManagerService extends IUserManager.Stub { } } + private boolean isPackageInstalled(String pkg, int userId) { + final ApplicationInfo info = mPm.getApplicationInfo(pkg, + PackageManager.GET_UNINSTALLED_PACKAGES, + userId); + if (info == null || (info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) { + return false; + } + return true; + } + + /** + * Removes all the restrictions files (res_<packagename>) for a given user, if all is true, + * else removes only those packages that have been uninstalled. + * Does not do any permissions checking. + */ + private void cleanAppRestrictions(int userId, boolean all) { + synchronized (mPackagesLock) { + File dir = Environment.getUserSystemDirectory(userId); + String[] files = dir.list(); + if (files == null) return; + for (String fileName : files) { + if (fileName.startsWith(RESTRICTIONS_FILE_PREFIX)) { + File resFile = new File(dir, fileName); + if (resFile.exists()) { + if (all) { + resFile.delete(); + } else { + String pkg = restrictionsFileNameToPackage(fileName); + if (!isPackageInstalled(pkg, userId)) { + resFile.delete(); + } + } + } + } + } + } + } + + /** + * Removes the app restrictions file for a specific package and user id, if it exists. + */ + private void cleanAppRestrictionsForPackage(String pkg, int userId) { + synchronized (mPackagesLock) { + File dir = Environment.getUserSystemDirectory(userId); + File resFile = new File(dir, packageToRestrictionsFileName(pkg)); + if (resFile.exists()) { + resFile.delete(); + } + } + } + @Override public UserInfo createUser(String name, int flags) { checkManageUsersPermission("Only the system can create users"); @@ -949,8 +1071,9 @@ public class UserManagerService extends IUserManager.Stub { } }, MINUTE_IN_MILLIS); + mRestrictionsPinStates.remove(userHandle); // Remove user file - AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + ".xml")); + AtomicFile userFile = new AtomicFile(new File(mUsersDir, userHandle + XML_SUFFIX)); userFile.delete(); // Update the user list writeUserListLocked(); @@ -999,6 +1122,171 @@ public class UserManagerService extends IUserManager.Stub { } } + @Override + public boolean setRestrictionsChallenge(String newPin) { + checkManageUsersPermission("Only system can modify the restrictions pin"); + int userId = UserHandle.getCallingUserId(); + synchronized (mPackagesLock) { + RestrictionsPinState pinState = mRestrictionsPinStates.get(userId); + if (pinState == null) { + pinState = new RestrictionsPinState(); + } + if (newPin == null) { + pinState.salt = 0; + pinState.pinHash = null; + } else { + try { + pinState.salt = SecureRandom.getInstance("SHA1PRNG").nextLong(); + } catch (NoSuchAlgorithmException e) { + pinState.salt = (long) (Math.random() * Long.MAX_VALUE); + } + pinState.pinHash = passwordToHash(newPin, pinState.salt); + pinState.failedAttempts = 0; + } + mRestrictionsPinStates.put(userId, pinState); + writeUserLocked(mUsers.get(userId)); + } + return true; + } + + @Override + public int checkRestrictionsChallenge(String pin) { + checkManageUsersPermission("Only system can verify the restrictions pin"); + int userId = UserHandle.getCallingUserId(); + synchronized (mPackagesLock) { + RestrictionsPinState pinState = mRestrictionsPinStates.get(userId); + // If there's no pin set, return error code + if (pinState == null || pinState.salt == 0 || pinState.pinHash == null) { + return UserManager.PIN_VERIFICATION_FAILED_NOT_SET; + } else if (pin == null) { + // If just checking if user can be prompted, return remaining time + int waitTime = getRemainingTimeForPinAttempt(pinState); + Slog.d(LOG_TAG, "Remaining waittime peek=" + waitTime); + return waitTime; + } else { + int waitTime = getRemainingTimeForPinAttempt(pinState); + Slog.d(LOG_TAG, "Remaining waittime=" + waitTime); + if (waitTime > 0) { + return waitTime; + } + if (passwordToHash(pin, pinState.salt).equals(pinState.pinHash)) { + pinState.failedAttempts = 0; + writeUserLocked(mUsers.get(userId)); + return UserManager.PIN_VERIFICATION_SUCCESS; + } else { + pinState.failedAttempts++; + pinState.lastAttemptTime = System.currentTimeMillis(); + writeUserLocked(mUsers.get(userId)); + return waitTime; + } + } + } + } + + private int getRemainingTimeForPinAttempt(RestrictionsPinState pinState) { + int backoffIndex = Math.min(pinState.failedAttempts / BACKOFF_INC_INTERVAL, + BACKOFF_TIMES.length - 1); + int backoffTime = (pinState.failedAttempts % BACKOFF_INC_INTERVAL) == 0 ? + BACKOFF_TIMES[backoffIndex] : 0; + return (int) Math.max(backoffTime + pinState.lastAttemptTime - System.currentTimeMillis(), + 0); + } + + @Override + public boolean hasRestrictionsChallenge() { + int userId = UserHandle.getCallingUserId(); + synchronized (mPackagesLock) { + return hasRestrictionsPinLocked(userId); + } + } + + private boolean hasRestrictionsPinLocked(int userId) { + RestrictionsPinState pinState = mRestrictionsPinStates.get(userId); + if (pinState == null || pinState.salt == 0 || pinState.pinHash == null) { + return false; + } + return true; + } + + @Override + public void removeRestrictions() { + checkManageUsersPermission("Only system can remove restrictions"); + final int userHandle = UserHandle.getCallingUserId(); + removeRestrictionsForUser(userHandle, true); + } + + private void removeRestrictionsForUser(final int userHandle, boolean unblockApps) { + synchronized (mPackagesLock) { + // Remove all user restrictions + setUserRestrictions(new Bundle(), userHandle); + // Remove restrictions pin + setRestrictionsChallenge(null); + // Remove any app restrictions + cleanAppRestrictions(userHandle, true); + } + if (unblockApps) { + unblockAllAppsForUser(userHandle); + } + } + + private void unblockAllAppsForUser(final int userHandle) { + mHandler.post(new Runnable() { + @Override + public void run() { + List<ApplicationInfo> apps = + mPm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES, + userHandle).getList(); + final long ident = Binder.clearCallingIdentity(); + try { + for (ApplicationInfo appInfo : apps) { + if ((appInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0 + && (appInfo.flags & ApplicationInfo.FLAG_BLOCKED) != 0) { + mPm.setApplicationBlockedSettingAsUser(appInfo.packageName, false, + userHandle); + } + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + }); + } + + /* + * Generate a hash for the given password. To avoid brute force attacks, we use a salted hash. + * Not the most secure, but it is at least a second level of protection. First level is that + * the file is in a location only readable by the system process. + * @param password the password. + * @param salt the randomly generated salt + * @return the hash of the pattern in a String. + */ + private String passwordToHash(String password, long salt) { + if (password == null) { + return null; + } + String algo = null; + String hashed = salt + password; + try { + byte[] saltedPassword = (password + salt).getBytes(); + byte[] sha1 = MessageDigest.getInstance(algo = "SHA-1").digest(saltedPassword); + byte[] md5 = MessageDigest.getInstance(algo = "MD5").digest(saltedPassword); + hashed = toHex(sha1) + toHex(md5); + } catch (NoSuchAlgorithmException e) { + Log.w(LOG_TAG, "Failed to encode string because of missing algorithm: " + algo); + } + return hashed; + } + + private static String toHex(byte[] ary) { + final String hex = "0123456789ABCDEF"; + String ret = ""; + for (int i = 0; i < ary.length; i++) { + ret += hex.charAt((ary[i] >> 4) & 0xf); + ret += hex.charAt(ary[i] & 0xf); + } + return ret; + } + private int getUidForPackage(String packageName) { long ident = Binder.clearCallingIdentity(); try { @@ -1020,7 +1308,7 @@ public class UserManagerService extends IUserManager.Stub { try { AtomicFile restrictionsFile = new AtomicFile(new File(Environment.getUserSystemDirectory(userId), - RESTRICTIONS_FILE_PREFIX + packageName + ".xml")); + packageToRestrictionsFileName(packageName))); fis = restrictionsFile.openRead(); XmlPullParser parser = Xml.newPullParser(); parser.setInput(fis, null); @@ -1081,7 +1369,7 @@ public class UserManagerService extends IUserManager.Stub { FileOutputStream fos = null; AtomicFile restrictionsFile = new AtomicFile( new File(Environment.getUserSystemDirectory(userId), - RESTRICTIONS_FILE_PREFIX + packageName + ".xml")); + packageToRestrictionsFileName(packageName))); try { fos = restrictionsFile.startWrite(); final BufferedOutputStream bos = new BufferedOutputStream(fos); @@ -1168,7 +1456,7 @@ public class UserManagerService extends IUserManager.Stub { } /** - * Make a note of the last started time of a user. + * Make a note of the last started time of a user and do some cleanup. * @param userId the user that was just foregrounded */ public void userForeground(int userId) { @@ -1183,6 +1471,12 @@ public class UserManagerService extends IUserManager.Stub { user.lastLoggedInTime = now; writeUserLocked(user); } + // If this is not a restricted profile and there is no restrictions pin, clean up + // all restrictions files that might have been left behind, else clean up just the + // ones with uninstalled packages + RestrictionsPinState pinState = mRestrictionsPinStates.get(userId); + final long salt = pinState == null ? 0 : pinState.salt; + cleanAppRestrictions(userId, (!user.isRestricted() && salt == 0)); } } @@ -1205,6 +1499,15 @@ public class UserManagerService extends IUserManager.Stub { } } + private String packageToRestrictionsFileName(String packageName) { + return RESTRICTIONS_FILE_PREFIX + packageName + XML_SUFFIX; + } + + private String restrictionsFileNameToPackage(String fileName) { + return fileName.substring(RESTRICTIONS_FILE_PREFIX.length(), + (int) (fileName.length() - XML_SUFFIX.length())); + } + @Override protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) { if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP) @@ -1249,4 +1552,17 @@ public class UserManagerService extends IUserManager.Stub { } } } + + private PackageMonitor mUserPackageMonitor = new PackageMonitor() { + @Override + public void onPackageRemoved(String pkg, int uid) { + final int userId = this.getChangingUserId(); + // Package could be disappearing because it is being blocked, so also check if + // it has been uninstalled. + final boolean uninstalled = isPackageDisappearing(pkg) == PACKAGE_PERMANENT_CHANGE; + if (uninstalled && userId >= 0 && !isPackageInstalled(pkg, userId)) { + cleanAppRestrictionsForPackage(pkg, userId); + } + } + }; } diff --git a/services/java/com/android/server/power/DisplayPowerController.java b/services/java/com/android/server/power/DisplayPowerController.java index b5010f2ac7b0..976a328c4ecb 100644 --- a/services/java/com/android/server/power/DisplayPowerController.java +++ b/services/java/com/android/server/power/DisplayPowerController.java @@ -29,7 +29,6 @@ import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -import android.hardware.SystemSensorManager; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -119,7 +118,7 @@ final class DisplayPowerController { // Proximity sensor debounce delay in milliseconds for positive or negative transitions. private static final int PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY = 0; - private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 500; + private static final int PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY = 250; // Trigger proximity if distance is less than 5 cm. private static final float TYPICAL_PROXIMITY_THRESHOLD = 5.0f; @@ -164,6 +163,10 @@ final class DisplayPowerController { // Notifier for sending asynchronous notifications. private final Notifier mNotifier; + // The display suspend blocker. + // Held while there are pending state change notifications. + private final SuspendBlocker mDisplaySuspendBlocker; + // The display blanker. private final DisplayBlanker mDisplayBlanker; @@ -271,7 +274,7 @@ final class DisplayPowerController { // The raw non-debounced proximity sensor state. private int mPendingProximity = PROXIMITY_UNKNOWN; - private long mPendingProximityDebounceTime; + private long mPendingProximityDebounceTime = -1; // -1 if fully debounced // True if the screen was turned off because of the proximity sensor. // When the screen turns on again, we report user activity to the power manager. @@ -346,10 +349,11 @@ final class DisplayPowerController { public DisplayPowerController(Looper looper, Context context, Notifier notifier, LightsService lights, TwilightService twilight, SensorManager sensorManager, DisplayManagerService displayManager, - DisplayBlanker displayBlanker, + SuspendBlocker displaySuspendBlocker, DisplayBlanker displayBlanker, Callbacks callbacks, Handler callbackHandler) { mHandler = new DisplayControllerHandler(looper); mNotifier = notifier; + mDisplaySuspendBlocker = displaySuspendBlocker; mDisplayBlanker = displayBlanker; mCallbacks = callbacks; mCallbackHandler = callbackHandler; @@ -601,7 +605,7 @@ final class DisplayPowerController { if (!mScreenOffBecauseOfProximity && mProximity == PROXIMITY_POSITIVE) { mScreenOffBecauseOfProximity = true; - sendOnProximityPositive(); + sendOnProximityPositiveWithWakelock(); setScreenOn(false); } } else if (mWaitingForNegativeProximity @@ -616,7 +620,7 @@ final class DisplayPowerController { if (mScreenOffBecauseOfProximity && mProximity != PROXIMITY_POSITIVE) { mScreenOffBecauseOfProximity = false; - sendOnProximityNegative(); + sendOnProximityNegativeWithWakelock(); } } else { mWaitingForNegativeProximity = false; @@ -737,7 +741,7 @@ final class DisplayPowerController { } } } - sendOnStateChanged(); + sendOnStateChangedWithWakelock(); } } @@ -810,49 +814,67 @@ final class DisplayPowerController { private void setProximitySensorEnabled(boolean enable) { if (enable) { if (!mProximitySensorEnabled) { + // Register the listener. + // Proximity sensor state already cleared initially. mProximitySensorEnabled = true; - mPendingProximity = PROXIMITY_UNKNOWN; mSensorManager.registerListener(mProximitySensorListener, mProximitySensor, SensorManager.SENSOR_DELAY_NORMAL, mHandler); } } else { if (mProximitySensorEnabled) { + // Unregister the listener. + // Clear the proximity sensor state for next time. mProximitySensorEnabled = false; mProximity = PROXIMITY_UNKNOWN; + mPendingProximity = PROXIMITY_UNKNOWN; mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED); mSensorManager.unregisterListener(mProximitySensorListener); + clearPendingProximityDebounceTime(); // release wake lock (must be last) } } } private void handleProximitySensorEvent(long time, boolean positive) { - if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) { - return; // no change - } - if (mPendingProximity == PROXIMITY_POSITIVE && positive) { - return; // no change - } + if (mProximitySensorEnabled) { + if (mPendingProximity == PROXIMITY_NEGATIVE && !positive) { + return; // no change + } + if (mPendingProximity == PROXIMITY_POSITIVE && positive) { + return; // no change + } - // Only accept a proximity sensor reading if it remains - // stable for the entire debounce delay. - mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED); - if (positive) { - mPendingProximity = PROXIMITY_POSITIVE; - mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY; - } else { - mPendingProximity = PROXIMITY_NEGATIVE; - mPendingProximityDebounceTime = time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY; + // Only accept a proximity sensor reading if it remains + // stable for the entire debounce delay. We hold a wake lock while + // debouncing the sensor. + mHandler.removeMessages(MSG_PROXIMITY_SENSOR_DEBOUNCED); + if (positive) { + mPendingProximity = PROXIMITY_POSITIVE; + setPendingProximityDebounceTime( + time + PROXIMITY_SENSOR_POSITIVE_DEBOUNCE_DELAY); // acquire wake lock + } else { + mPendingProximity = PROXIMITY_NEGATIVE; + setPendingProximityDebounceTime( + time + PROXIMITY_SENSOR_NEGATIVE_DEBOUNCE_DELAY); // acquire wake lock + } + + // Debounce the new sensor reading. + debounceProximitySensor(); } - debounceProximitySensor(); } private void debounceProximitySensor() { - if (mPendingProximity != PROXIMITY_UNKNOWN) { + if (mProximitySensorEnabled + && mPendingProximity != PROXIMITY_UNKNOWN + && mPendingProximityDebounceTime >= 0) { final long now = SystemClock.uptimeMillis(); if (mPendingProximityDebounceTime <= now) { + // Sensor reading accepted. Apply the change then release the wake lock. mProximity = mPendingProximity; - sendUpdatePowerState(); + updatePowerState(); + clearPendingProximityDebounceTime(); // release wake lock (must be last) } else { + // Need to wait a little longer. + // Debounce again later. We continue holding a wake lock while waiting. Message msg = mHandler.obtainMessage(MSG_PROXIMITY_SENSOR_DEBOUNCED); msg.setAsynchronous(true); mHandler.sendMessageAtTime(msg, mPendingProximityDebounceTime); @@ -860,6 +882,20 @@ final class DisplayPowerController { } } + private void clearPendingProximityDebounceTime() { + if (mPendingProximityDebounceTime >= 0) { + mPendingProximityDebounceTime = -1; + mDisplaySuspendBlocker.release(); // release wake lock + } + } + + private void setPendingProximityDebounceTime(long debounceTime) { + if (mPendingProximityDebounceTime < 0) { + mDisplaySuspendBlocker.acquire(); // acquire wake lock + } + mPendingProximityDebounceTime = debounceTime; + } + private void setLightSensorEnabled(boolean enable, boolean updateAutoBrightness) { if (enable) { if (!mLightSensorEnabled) { @@ -1120,7 +1156,8 @@ final class DisplayPowerController { return x + (y - x) * alpha; } - private void sendOnStateChanged() { + private void sendOnStateChangedWithWakelock() { + mDisplaySuspendBlocker.acquire(); mCallbackHandler.post(mOnStateChangedRunnable); } @@ -1128,10 +1165,12 @@ final class DisplayPowerController { @Override public void run() { mCallbacks.onStateChanged(); + mDisplaySuspendBlocker.release(); } }; - private void sendOnProximityPositive() { + private void sendOnProximityPositiveWithWakelock() { + mDisplaySuspendBlocker.acquire(); mCallbackHandler.post(mOnProximityPositiveRunnable); } @@ -1139,10 +1178,12 @@ final class DisplayPowerController { @Override public void run() { mCallbacks.onProximityPositive(); + mDisplaySuspendBlocker.release(); } }; - private void sendOnProximityNegative() { + private void sendOnProximityNegativeWithWakelock() { + mDisplaySuspendBlocker.acquire(); mCallbackHandler.post(mOnProximityNegativeRunnable); } @@ -1150,6 +1191,7 @@ final class DisplayPowerController { @Override public void run() { mCallbacks.onProximityNegative(); + mDisplaySuspendBlocker.release(); } }; diff --git a/services/java/com/android/server/power/ElectronBeam.java b/services/java/com/android/server/power/ElectronBeam.java index 379e704ab1d9..729bd1669e83 100644 --- a/services/java/com/android/server/power/ElectronBeam.java +++ b/services/java/com/android/server/power/ElectronBeam.java @@ -35,6 +35,7 @@ import android.util.FloatMath; import android.util.Slog; import android.view.Display; import android.view.DisplayInfo; +import android.view.Surface.OutOfResourcesException; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -94,7 +95,7 @@ final class ElectronBeam { // Texture names. We only use one texture, which contains the screenshot. private final int[] mTexNames = new int[1]; private boolean mTexNamesGenerated; - private float mTexMatrix[] = new float[16]; + private final float mTexMatrix[] = new float[16]; // Vertex and corresponding texture coordinates. // We have 4 2D vertices, so 8 elements. The vertices form a quad. @@ -319,10 +320,10 @@ final class ElectronBeam { /** * Draws a frame where the electron beam has been stretched out into - * a thin white horizontal line that fades as it expands outwards. + * a thin white horizontal line that fades as it collapses inwards. * - * @param stretch The stretch factor. 0.0 is no stretch / no fade, - * 1.0 is maximum stretch / maximum fade. + * @param stretch The stretch factor. 0.0 is maximum stretch / no fade, + * 1.0 is collapsed / maximum fade. */ private void drawHStretch(float stretch) { // compute interpolation scale factor @@ -338,7 +339,7 @@ final class ElectronBeam { // draw narrow fading white line setHStretchQuad(mVertexBuffer, mDisplayWidth, mDisplayHeight, ag); - GLES10.glColor4f(1.0f - ag, 1.0f - ag, 1.0f - ag, 1.0f); + GLES10.glColor4f(1.0f - ag*0.75f, 1.0f - ag*0.75f, 1.0f - ag*0.75f, 1.0f); GLES10.glDrawArrays(GLES10.GL_TRIANGLE_FAN, 0, 4); // clean up @@ -355,7 +356,7 @@ final class ElectronBeam { } private static void setHStretchQuad(FloatBuffer vtx, float dw, float dh, float a) { - final float w = dw + (dw * a); + final float w = 2 * dw * (1.0f - a); final float h = 1.0f; final float x = (dw - w) * 0.5f; final float y = (dh - h) * 0.5f; @@ -515,7 +516,7 @@ final class ElectronBeam { mSurfaceControl = new SurfaceControl(mSurfaceSession, "ElectronBeam", mDisplayWidth, mDisplayHeight, PixelFormat.OPAQUE, flags); - } catch (SurfaceControl.OutOfResourcesException ex) { + } catch (OutOfResourcesException ex) { Slog.e(TAG, "Unable to create surface.", ex); return false; } @@ -525,7 +526,7 @@ final class ElectronBeam { mSurfaceControl.setSize(mDisplayWidth, mDisplayHeight); mSurface = new Surface(); mSurface.copyFrom(mSurfaceControl); - + mSurfaceLayout = new NaturalSurfaceLayout(mDisplayManager, mSurfaceControl); mSurfaceLayout.onDisplayTransaction(); } finally { diff --git a/services/java/com/android/server/power/Notifier.java b/services/java/com/android/server/power/Notifier.java index d99d523f5cf5..264e2e9c3b15 100644 --- a/services/java/com/android/server/power/Notifier.java +++ b/services/java/com/android/server/power/Notifier.java @@ -16,6 +16,8 @@ package com.android.server.power; +import android.app.AppOpsManager; +import com.android.internal.app.IAppOpsService; import com.android.internal.app.IBatteryStats; import com.android.server.EventLogTags; @@ -75,6 +77,7 @@ final class Notifier { private final Context mContext; private final IBatteryStats mBatteryStats; + private final IAppOpsService mAppOps; private final SuspendBlocker mSuspendBlocker; private final ScreenOnBlocker mScreenOnBlocker; private final WindowManagerPolicy mPolicy; @@ -104,10 +107,11 @@ final class Notifier { private boolean mScreenOnBlockerAcquired; public Notifier(Looper looper, Context context, IBatteryStats batteryStats, - SuspendBlocker suspendBlocker, ScreenOnBlocker screenOnBlocker, + IAppOpsService appOps, SuspendBlocker suspendBlocker, ScreenOnBlocker screenOnBlocker, WindowManagerPolicy policy) { mContext = context; mBatteryStats = batteryStats; + mAppOps = appOps; mSuspendBlocker = suspendBlocker; mScreenOnBlocker = screenOnBlocker; mPolicy = policy; @@ -124,11 +128,12 @@ final class Notifier { /** * Called when a wake lock is acquired. */ - public void onWakeLockAcquired(int flags, String tag, int ownerUid, int ownerPid, - WorkSource workSource) { + public void onWakeLockAcquired(int flags, String tag, String packageName, + int ownerUid, int ownerPid, WorkSource workSource) { if (DEBUG) { Slog.d(TAG, "onWakeLockAcquired: flags=" + flags + ", tag=\"" + tag - + "\", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid + + "\", packageName=" + packageName + + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid + ", workSource=" + workSource); } @@ -138,6 +143,9 @@ final class Notifier { mBatteryStats.noteStartWakelockFromSource(workSource, ownerPid, tag, monitorType); } else { mBatteryStats.noteStartWakelock(ownerUid, ownerPid, tag, monitorType); + // XXX need to deal with disabled operations. + mAppOps.startOperation(AppOpsManager.getToken(mAppOps), + AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName); } } catch (RemoteException ex) { // Ignore @@ -147,11 +155,12 @@ final class Notifier { /** * Called when a wake lock is released. */ - public void onWakeLockReleased(int flags, String tag, int ownerUid, int ownerPid, - WorkSource workSource) { + public void onWakeLockReleased(int flags, String tag, String packageName, + int ownerUid, int ownerPid, WorkSource workSource) { if (DEBUG) { Slog.d(TAG, "onWakeLockReleased: flags=" + flags + ", tag=\"" + tag - + "\", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid + + "\", packageName=" + packageName + + ", ownerUid=" + ownerUid + ", ownerPid=" + ownerPid + ", workSource=" + workSource); } @@ -161,6 +170,8 @@ final class Notifier { mBatteryStats.noteStopWakelockFromSource(workSource, ownerPid, tag, monitorType); } else { mBatteryStats.noteStopWakelock(ownerUid, ownerPid, tag, monitorType); + mAppOps.finishOperation(AppOpsManager.getToken(mAppOps), + AppOpsManager.OP_WAKE_LOCK, ownerUid, packageName); } } catch (RemoteException ex) { // Ignore diff --git a/services/java/com/android/server/power/PowerManagerService.java b/services/java/com/android/server/power/PowerManagerService.java index 6f70712db20b..8fbde14897e8 100644 --- a/services/java/com/android/server/power/PowerManagerService.java +++ b/services/java/com/android/server/power/PowerManagerService.java @@ -16,6 +16,7 @@ package com.android.server.power; +import com.android.internal.app.IAppOpsService; import com.android.internal.app.IBatteryStats; import com.android.server.BatteryService; import com.android.server.EventLogTags; @@ -50,6 +51,7 @@ import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; +import android.os.SystemProperties; import android.os.SystemService; import android.os.UserHandle; import android.os.WorkSource; @@ -61,7 +63,6 @@ import android.util.TimeUtils; import android.view.WindowManagerPolicy; import java.io.FileDescriptor; -import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; @@ -171,6 +172,7 @@ public final class PowerManagerService extends IPowerManager.Stub private BatteryService mBatteryService; private DisplayManagerService mDisplayManagerService; private IBatteryStats mBatteryStats; + private IAppOpsService mAppOps; private HandlerThread mHandlerThread; private PowerManagerHandler mHandler; private WindowManagerPolicy mPolicy; @@ -284,6 +286,9 @@ public final class PowerManagerService extends IPowerManager.Stub // True if the device should wake up when plugged or unplugged. private boolean mWakeUpWhenPluggedOrUnpluggedConfig; + // True if the device should suspend when the screen is off due to proximity. + private boolean mSuspendWhenScreenOffDueToProximityConfig; + // True if dreams are supported on this device. private boolean mDreamsSupportedConfig; @@ -364,8 +369,6 @@ public final class PowerManagerService extends IPowerManager.Stub private long mLastWarningAboutUserActivityPermission = Long.MIN_VALUE; private native void nativeInit(); - private static native void nativeShutdown(); - private static native void nativeReboot(String reason) throws IOException; private static native void nativeSetPowerState(boolean screenOn, boolean screenBright); private static native void nativeAcquireSuspendBlocker(String name); @@ -395,17 +398,19 @@ public final class PowerManagerService extends IPowerManager.Stub */ public void init(Context context, LightsService ls, ActivityManagerService am, BatteryService bs, IBatteryStats bss, - DisplayManagerService dm) { + IAppOpsService appOps, DisplayManagerService dm) { mContext = context; mLightsService = ls; mBatteryService = bs; mBatteryStats = bss; + mAppOps = appOps; mDisplayManagerService = dm; mHandlerThread = new HandlerThread(TAG); mHandlerThread.start(); mHandler = new PowerManagerHandler(mHandlerThread.getLooper()); Watchdog.getInstance().addMonitor(this); + Watchdog.getInstance().addThread(mHandler, mHandlerThread.getName()); // Forcibly turn the screen on at boot so that it is in a known power state. // We do this in init() rather than in the constructor because setting the @@ -437,18 +442,19 @@ public final class PowerManagerService extends IPowerManager.Stub // The notifier runs on the system server's main looper so as not to interfere // with the animations and other critical functions of the power manager. mNotifier = new Notifier(Looper.getMainLooper(), mContext, mBatteryStats, - createSuspendBlockerLocked("PowerManagerService.Broadcasts"), + mAppOps, createSuspendBlockerLocked("PowerManagerService.Broadcasts"), mScreenOnBlocker, mPolicy); // The display power controller runs on the power manager service's // own handler thread to ensure timely operation. mDisplayPowerController = new DisplayPowerController(mHandler.getLooper(), mContext, mNotifier, mLightsService, twilight, sensorManager, - mDisplayManagerService, mDisplayBlanker, + mDisplayManagerService, mDisplaySuspendBlocker, mDisplayBlanker, mDisplayPowerControllerCallbacks, mHandler); mWirelessChargerDetector = new WirelessChargerDetector(sensorManager, - createSuspendBlockerLocked("PowerManagerService.WirelessChargerDetector")); + createSuspendBlockerLocked("PowerManagerService.WirelessChargerDetector"), + mHandler); mSettingsObserver = new SettingsObserver(mHandler); mAttentionLight = mLightsService.getLight(LightsService.LIGHT_ID_ATTENTION); @@ -511,6 +517,8 @@ public final class PowerManagerService extends IPowerManager.Stub mWakeUpWhenPluggedOrUnpluggedConfig = resources.getBoolean( com.android.internal.R.bool.config_unplugTurnsOnScreen); + mSuspendWhenScreenOffDueToProximityConfig = resources.getBoolean( + com.android.internal.R.bool.config_suspendWhenScreenOffDueToProximity); mDreamsSupportedConfig = resources.getBoolean( com.android.internal.R.bool.config_dreamsSupported); mDreamsEnabledByDefaultConfig = resources.getBoolean( @@ -572,10 +580,20 @@ public final class PowerManagerService extends IPowerManager.Stub } @Override // Binder call - public void acquireWakeLock(IBinder lock, int flags, String tag, WorkSource ws) { + public void acquireWakeLockWithUid(IBinder lock, int flags, String tag, String packageName, + int uid) { + acquireWakeLock(lock, flags, tag, packageName, new WorkSource(uid)); + } + + @Override // Binder call + public void acquireWakeLock(IBinder lock, int flags, String tag, String packageName, + WorkSource ws) { if (lock == null) { throw new IllegalArgumentException("lock must not be null"); } + if (packageName == null) { + throw new IllegalArgumentException("packageName must not be null"); + } PowerManager.validateWakeLockParameters(flags, tag); mContext.enforceCallingOrSelfPermission(android.Manifest.permission.WAKE_LOCK, null); @@ -590,14 +608,14 @@ public final class PowerManagerService extends IPowerManager.Stub final int pid = Binder.getCallingPid(); final long ident = Binder.clearCallingIdentity(); try { - acquireWakeLockInternal(lock, flags, tag, ws, uid, pid); + acquireWakeLockInternal(lock, flags, tag, packageName, ws, uid, pid); } finally { Binder.restoreCallingIdentity(ident); } } - private void acquireWakeLockInternal(IBinder lock, int flags, String tag, WorkSource ws, - int uid, int pid) { + private void acquireWakeLockInternal(IBinder lock, int flags, String tag, String packageName, + WorkSource ws, int uid, int pid) { synchronized (mLock) { if (DEBUG_SPEW) { Slog.d(TAG, "acquireWakeLockInternal: lock=" + Objects.hashCode(lock) @@ -612,11 +630,11 @@ public final class PowerManagerService extends IPowerManager.Stub if (!wakeLock.hasSameProperties(flags, tag, ws, uid, pid)) { // Update existing wake lock. This shouldn't happen but is harmless. notifyWakeLockReleasedLocked(wakeLock); - wakeLock.updateProperties(flags, tag, ws, uid, pid); + wakeLock.updateProperties(flags, tag, packageName, ws, uid, pid); notifyWakeLockAcquiredLocked(wakeLock); } } else { - wakeLock = new WakeLock(lock, flags, tag, ws, uid, pid); + wakeLock = new WakeLock(lock, flags, tag, packageName, ws, uid, pid); try { lock.linkToDeath(wakeLock, 0); } catch (RemoteException ex) { @@ -632,6 +650,7 @@ public final class PowerManagerService extends IPowerManager.Stub } } + @SuppressWarnings("deprecation") private static boolean isScreenLock(final WakeLock wakeLock) { switch (wakeLock.mFlags & PowerManager.WAKE_LOCK_LEVEL_MASK) { case PowerManager.FULL_WAKE_LOCK: @@ -643,8 +662,8 @@ public final class PowerManagerService extends IPowerManager.Stub } private void applyWakeLockFlagsOnAcquireLocked(WakeLock wakeLock) { - if ((wakeLock.mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0 && - isScreenLock(wakeLock)) { + if ((wakeLock.mFlags & PowerManager.ACQUIRE_CAUSES_WAKEUP) != 0 + && isScreenLock(wakeLock)) { wakeUpNoUpdateLocked(SystemClock.uptimeMillis()); } } @@ -718,7 +737,8 @@ public final class PowerManagerService extends IPowerManager.Stub } private void applyWakeLockFlagsOnReleaseLocked(WakeLock wakeLock) { - if ((wakeLock.mFlags & PowerManager.ON_AFTER_RELEASE) != 0) { + if ((wakeLock.mFlags & PowerManager.ON_AFTER_RELEASE) != 0 + && isScreenLock(wakeLock)) { userActivityNoUpdateLocked(SystemClock.uptimeMillis(), PowerManager.USER_ACTIVITY_EVENT_OTHER, PowerManager.USER_ACTIVITY_FLAG_NO_CHANGE_LIGHTS, @@ -785,14 +805,16 @@ public final class PowerManagerService extends IPowerManager.Stub private void notifyWakeLockAcquiredLocked(WakeLock wakeLock) { if (mSystemReady) { - mNotifier.onWakeLockAcquired(wakeLock.mFlags, wakeLock.mTag, + wakeLock.mNotifiedAcquired = true; + mNotifier.onWakeLockAcquired(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource); } } private void notifyWakeLockReleasedLocked(WakeLock wakeLock) { - if (mSystemReady) { - mNotifier.onWakeLockReleased(wakeLock.mFlags, wakeLock.mTag, + if (mSystemReady && wakeLock.mNotifiedAcquired) { + wakeLock.mNotifiedAcquired = false; + mNotifier.onWakeLockReleased(wakeLock.mFlags, wakeLock.mTag, wakeLock.mPackageName, wakeLock.mOwnerUid, wakeLock.mOwnerPid, wakeLock.mWorkSource); } } @@ -807,6 +829,7 @@ public final class PowerManagerService extends IPowerManager.Stub } } + @SuppressWarnings("deprecation") private boolean isWakeLockLevelSupportedInternal(int level) { synchronized (mLock) { switch (level) { @@ -995,6 +1018,7 @@ public final class PowerManagerService extends IPowerManager.Stub } } + @SuppressWarnings("deprecation") private boolean goToSleepNoUpdateLocked(long eventTime, int reason) { if (DEBUG_SPEW) { Slog.d(TAG, "goToSleepNoUpdateLocked: eventTime=" + eventTime + ", reason=" + reason); @@ -1251,6 +1275,7 @@ public final class PowerManagerService extends IPowerManager.Stub * * This function must have no other side-effects. */ + @SuppressWarnings("deprecation") private void updateWakeLockSummaryLocked(int dirty) { if ((dirty & (DIRTY_WAKE_LOCKS | DIRTY_WAKEFULNESS)) != 0) { mWakeLockSummary = 0; @@ -1289,7 +1314,7 @@ public final class PowerManagerService extends IPowerManager.Stub break; case PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK: if (mWakefulness != WAKEFULNESS_ASLEEP) { - mWakeLockSummary |= WAKE_LOCK_CPU | WAKE_LOCK_PROXIMITY_SCREEN_OFF; + mWakeLockSummary |= WAKE_LOCK_PROXIMITY_SCREEN_OFF; } break; } @@ -1448,7 +1473,11 @@ public final class PowerManagerService extends IPowerManager.Stub /** * Returns true if the device is being kept awake by a wake lock, user activity - * or the stay on while powered setting. + * or the stay on while powered setting. We also keep the phone awake when + * the proximity sensor returns a positive result so that the device does not + * lock while in a phone call. This function only controls whether the device + * will go to sleep or dream which is independent of whether it will be allowed + * to suspend. */ private boolean isBeingKeptAwakeLocked() { return mStayOn @@ -1739,10 +1768,8 @@ public final class PowerManagerService extends IPowerManager.Stub * This function must have no other side-effects. */ private void updateSuspendBlockerLocked() { - final boolean needWakeLockSuspendBlocker = (mWakeLockSummary != 0); - final boolean needDisplaySuspendBlocker = (mUserActivitySummary != 0 - || mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF - || !mDisplayReady || !mBootCompleted); + final boolean needWakeLockSuspendBlocker = ((mWakeLockSummary & WAKE_LOCK_CPU) != 0); + final boolean needDisplaySuspendBlocker = needDisplaySuspendBlocker(); // First acquire suspend blockers if needed. if (needWakeLockSuspendBlocker && !mHoldingWakeLockSuspendBlocker) { @@ -1765,6 +1792,27 @@ public final class PowerManagerService extends IPowerManager.Stub } } + /** + * Return true if we must keep a suspend blocker active on behalf of the display. + * We do so if the screen is on or is in transition between states. + */ + private boolean needDisplaySuspendBlocker() { + if (!mDisplayReady) { + return true; + } + if (mDisplayPowerRequest.screenState != DisplayPowerRequest.SCREEN_STATE_OFF) { + // If we asked for the screen to be on but it is off due to the proximity + // sensor then we may suspend but only if the configuration allows it. + // On some hardware it may not be safe to suspend because the proximity + // sensor may not be correctly configured as a wake-up source. + if (!mDisplayPowerRequest.useProximitySensor || !mProximityPositive + || !mSuspendWhenScreenOffDueToProximityConfig) { + return true; + } + } + return false; + } + @Override // Binder call public boolean isScreenOn() { final long ident = Binder.clearCallingIdentity(); @@ -2105,7 +2153,7 @@ public final class PowerManagerService extends IPowerManager.Stub * * @param brightness The overridden brightness. * - * @see Settings.System#SCREEN_BRIGHTNESS + * @see android.provider.Settings.System#SCREEN_BRIGHTNESS */ @Override // Binder call public void setTemporaryScreenBrightnessSettingOverride(int brightness) { @@ -2170,18 +2218,26 @@ public final class PowerManagerService extends IPowerManager.Stub * to be clean. Most people should use {@link ShutdownThread} for a clean shutdown. */ public static void lowLevelShutdown() { - nativeShutdown(); + SystemProperties.set("sys.powerctl", "shutdown"); } /** - * Low-level function to reboot the device. + * Low-level function to reboot the device. On success, this function + * doesn't return. If more than 5 seconds passes from the time, + * a reboot is requested, this method returns. * * @param reason code to pass to the kernel (e.g. "recovery"), or null. - * @throws IOException if reboot fails for some reason (eg, lack of - * permission) */ - public static void lowLevelReboot(String reason) throws IOException { - nativeReboot(reason); + public static void lowLevelReboot(String reason) { + if (reason == null) { + reason = ""; + } + SystemProperties.set("sys.powerctl", "reboot," + reason); + try { + Thread.sleep(20000); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } } @Override // Watchdog.Monitor implementation @@ -2237,7 +2293,16 @@ public final class PowerManagerService extends IPowerManager.Stub pw.println(); pw.println("Settings and Configuration:"); + pw.println(" mWakeUpWhenPluggedOrUnpluggedConfig=" + + mWakeUpWhenPluggedOrUnpluggedConfig); + pw.println(" mSuspendWhenScreenOffDueToProximityConfig=" + + mSuspendWhenScreenOffDueToProximityConfig); pw.println(" mDreamsSupportedConfig=" + mDreamsSupportedConfig); + pw.println(" mDreamsEnabledByDefaultConfig=" + mDreamsEnabledByDefaultConfig); + pw.println(" mDreamsActivatedOnSleepByDefaultConfig=" + + mDreamsActivatedOnSleepByDefaultConfig); + pw.println(" mDreamsActivatedOnDockByDefaultConfig=" + + mDreamsActivatedOnDockByDefaultConfig); pw.println(" mDreamsEnabledSetting=" + mDreamsEnabledSetting); pw.println(" mDreamsActivateOnSleepSetting=" + mDreamsActivateOnSleepSetting); pw.println(" mDreamsActivateOnDockSetting=" + mDreamsActivateOnDockSetting); @@ -2426,15 +2491,18 @@ public final class PowerManagerService extends IPowerManager.Stub public final IBinder mLock; public int mFlags; public String mTag; + public final String mPackageName; public WorkSource mWorkSource; - public int mOwnerUid; - public int mOwnerPid; + public final int mOwnerUid; + public final int mOwnerPid; + public boolean mNotifiedAcquired; - public WakeLock(IBinder lock, int flags, String tag, WorkSource workSource, - int ownerUid, int ownerPid) { + public WakeLock(IBinder lock, int flags, String tag, String packageName, + WorkSource workSource, int ownerUid, int ownerPid) { mLock = lock; mFlags = flags; mTag = tag; + mPackageName = packageName; mWorkSource = copyWorkSource(workSource); mOwnerUid = ownerUid; mOwnerPid = ownerPid; @@ -2454,13 +2522,23 @@ public final class PowerManagerService extends IPowerManager.Stub && mOwnerPid == ownerPid; } - public void updateProperties(int flags, String tag, WorkSource workSource, - int ownerUid, int ownerPid) { + public void updateProperties(int flags, String tag, String packageName, + WorkSource workSource, int ownerUid, int ownerPid) { + if (!mPackageName.equals(packageName)) { + throw new IllegalStateException("Existing wake lock package name changed: " + + mPackageName + " to " + packageName); + } + if (mOwnerUid != ownerUid) { + throw new IllegalStateException("Existing wake lock uid changed: " + + mOwnerUid + " to " + ownerUid); + } + if (mOwnerPid != ownerPid) { + throw new IllegalStateException("Existing wake lock pid changed: " + + mOwnerPid + " to " + ownerPid); + } mFlags = flags; mTag = tag; updateWorkSource(workSource); - mOwnerUid = ownerUid; - mOwnerPid = ownerPid; } public boolean hasSameWorkSource(WorkSource workSource) { diff --git a/services/java/com/android/server/power/ShutdownThread.java b/services/java/com/android/server/power/ShutdownThread.java index c084666d62c6..88a27f5f74dd 100644 --- a/services/java/com/android/server/power/ShutdownThread.java +++ b/services/java/com/android/server/power/ShutdownThread.java @@ -492,11 +492,8 @@ public final class ShutdownThread extends Thread { public static void rebootOrShutdown(boolean reboot, String reason) { if (reboot) { Log.i(TAG, "Rebooting, reason: " + reason); - try { - PowerManagerService.lowLevelReboot(reason); - } catch (Exception e) { - Log.e(TAG, "Reboot failed, will attempt shutdown instead", e); - } + PowerManagerService.lowLevelReboot(reason); + Log.e(TAG, "Reboot failed, will attempt shutdown instead"); } else if (SHUTDOWN_VIBRATE_MS > 0) { // vibrate before shutting down Vibrator vibrator = new SystemVibrator(); diff --git a/services/java/com/android/server/power/WirelessChargerDetector.java b/services/java/com/android/server/power/WirelessChargerDetector.java index ac6dc3e6f299..38f5d7740842 100644 --- a/services/java/com/android/server/power/WirelessChargerDetector.java +++ b/services/java/com/android/server/power/WirelessChargerDetector.java @@ -21,7 +21,11 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.os.BatteryManager; +import android.os.Handler; +import android.os.Message; +import android.os.SystemClock; import android.util.Slog; +import android.util.TimeUtils; import java.io.PrintWriter; @@ -69,12 +73,12 @@ final class WirelessChargerDetector { private static final String TAG = "WirelessChargerDetector"; private static final boolean DEBUG = false; - // Number of nanoseconds per millisecond. - private static final long NANOS_PER_MS = 1000000; - // The minimum amount of time to spend watching the sensor before making // a determination of whether movement occurred. - private static final long SETTLE_TIME_NANOS = 500 * NANOS_PER_MS; + private static final long SETTLE_TIME_MILLIS = 800; + + // The sensor sampling interval. + private static final int SAMPLING_INTERVAL_MILLIS = 50; // The minimum number of samples that must be collected. private static final int MIN_SAMPLES = 3; @@ -96,6 +100,7 @@ final class WirelessChargerDetector { private final SensorManager mSensorManager; private final SuspendBlocker mSuspendBlocker; + private final Handler mHandler; // The gravity sensor, or null if none. private Sensor mGravitySensor; @@ -115,6 +120,9 @@ final class WirelessChargerDetector { // The suspend blocker is held while this is the case. private boolean mDetectionInProgress; + // The time when detection was last performed. + private long mDetectionStartTime; + // True if the rest position should be updated if at rest. // Otherwise, the current rest position is simply checked and cleared if movement // is detected but no new rest position is stored. @@ -126,14 +134,17 @@ final class WirelessChargerDetector { // The number of samples collected that showed evidence of not being at rest. private int mMovingSamples; - // The time and value of the first sample that was collected. - private long mFirstSampleTime; + // The value of the first sample that was collected. private float mFirstSampleX, mFirstSampleY, mFirstSampleZ; + // The value of the last sample that was collected. + private float mLastSampleX, mLastSampleY, mLastSampleZ; + public WirelessChargerDetector(SensorManager sensorManager, - SuspendBlocker suspendBlocker) { + SuspendBlocker suspendBlocker, Handler handler) { mSensorManager = sensorManager; mSuspendBlocker = suspendBlocker; + mHandler = handler; mGravitySensor = sensorManager.getDefaultSensor(Sensor.TYPE_GRAVITY); } @@ -147,12 +158,15 @@ final class WirelessChargerDetector { pw.println(" mAtRest=" + mAtRest); pw.println(" mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ); pw.println(" mDetectionInProgress=" + mDetectionInProgress); + pw.println(" mDetectionStartTime=" + (mDetectionStartTime == 0 ? "0 (never)" + : TimeUtils.formatUptime(mDetectionStartTime))); pw.println(" mMustUpdateRestPosition=" + mMustUpdateRestPosition); pw.println(" mTotalSamples=" + mTotalSamples); pw.println(" mMovingSamples=" + mMovingSamples); - pw.println(" mFirstSampleTime=" + mFirstSampleTime); pw.println(" mFirstSampleX=" + mFirstSampleX + ", mFirstSampleY=" + mFirstSampleY + ", mFirstSampleZ=" + mFirstSampleZ); + pw.println(" mLastSampleX=" + mLastSampleX + + ", mLastSampleY=" + mLastSampleY + ", mLastSampleZ=" + mLastSampleZ); } } @@ -209,25 +223,63 @@ final class WirelessChargerDetector { private void startDetectionLocked() { if (!mDetectionInProgress && mGravitySensor != null) { if (mSensorManager.registerListener(mListener, mGravitySensor, - SensorManager.SENSOR_DELAY_UI)) { + SAMPLING_INTERVAL_MILLIS * 1000)) { mSuspendBlocker.acquire(); mDetectionInProgress = true; + mDetectionStartTime = SystemClock.uptimeMillis(); mTotalSamples = 0; mMovingSamples = 0; + + Message msg = Message.obtain(mHandler, mSensorTimeout); + msg.setAsynchronous(true); + mHandler.sendMessageDelayed(msg, SETTLE_TIME_MILLIS); } } } - private void processSample(long timeNanos, float x, float y, float z) { - synchronized (mLock) { - if (!mDetectionInProgress) { - return; + private void finishDetectionLocked() { + if (mDetectionInProgress) { + mSensorManager.unregisterListener(mListener); + mHandler.removeCallbacks(mSensorTimeout); + + if (mMustUpdateRestPosition) { + clearAtRestLocked(); + if (mTotalSamples < MIN_SAMPLES) { + Slog.w(TAG, "Wireless charger detector is broken. Only received " + + mTotalSamples + " samples from the gravity sensor but we " + + "need at least " + MIN_SAMPLES + " and we expect to see " + + "about " + SETTLE_TIME_MILLIS / SAMPLING_INTERVAL_MILLIS + + " on average."); + } else if (mMovingSamples == 0) { + mAtRest = true; + mRestX = mLastSampleX; + mRestY = mLastSampleY; + mRestZ = mLastSampleZ; + } + mMustUpdateRestPosition = false; + } + + if (DEBUG) { + Slog.d(TAG, "New state: mAtRest=" + mAtRest + + ", mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ + + ", mTotalSamples=" + mTotalSamples + + ", mMovingSamples=" + mMovingSamples); } + mDetectionInProgress = false; + mSuspendBlocker.release(); + } + } + + private void processSampleLocked(float x, float y, float z) { + if (mDetectionInProgress) { + mLastSampleX = x; + mLastSampleY = y; + mLastSampleZ = z; + mTotalSamples += 1; if (mTotalSamples == 1) { // Save information about the first sample collected. - mFirstSampleTime = timeNanos; mFirstSampleX = x; mFirstSampleY = y; mFirstSampleZ = z; @@ -247,32 +299,6 @@ final class WirelessChargerDetector { } clearAtRestLocked(); } - - // Save the result when done. - if (timeNanos - mFirstSampleTime >= SETTLE_TIME_NANOS - && mTotalSamples >= MIN_SAMPLES) { - mSensorManager.unregisterListener(mListener); - if (mMustUpdateRestPosition) { - if (mMovingSamples == 0) { - mAtRest = true; - mRestX = x; - mRestY = y; - mRestZ = z; - } else { - clearAtRestLocked(); - } - mMustUpdateRestPosition = false; - } - mDetectionInProgress = false; - mSuspendBlocker.release(); - - if (DEBUG) { - Slog.d(TAG, "New state: mAtRest=" + mAtRest - + ", mRestX=" + mRestX + ", mRestY=" + mRestY + ", mRestZ=" + mRestZ - + ", mTotalSamples=" + mTotalSamples - + ", mMovingSamples=" + mMovingSamples); - } - } } } @@ -310,11 +336,22 @@ final class WirelessChargerDetector { private final SensorEventListener mListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent event) { - processSample(event.timestamp, event.values[0], event.values[1], event.values[2]); + synchronized (mLock) { + processSampleLocked(event.values[0], event.values[1], event.values[2]); + } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } }; + + private final Runnable mSensorTimeout = new Runnable() { + @Override + public void run() { + synchronized (mLock) { + finishDetectionLocked(); + } + } + }; } diff --git a/services/java/com/android/server/print/PrintManagerService.java b/services/java/com/android/server/print/PrintManagerService.java new file mode 100644 index 000000000000..edd6b2588287 --- /dev/null +++ b/services/java/com/android/server/print/PrintManagerService.java @@ -0,0 +1,651 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.print; + +import android.Manifest; +import android.app.Notification; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.content.BroadcastReceiver; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.database.ContentObserver; +import android.net.Uri; +import android.os.Binder; +import android.os.Bundle; +import android.os.Process; +import android.os.RemoteException; +import android.os.UserHandle; +import android.print.IPrintDocumentAdapter; +import android.print.IPrintJobStateChangeListener; +import android.print.IPrintManager; +import android.print.IPrinterDiscoveryObserver; +import android.print.PrintAttributes; +import android.print.PrintJobId; +import android.print.PrintJobInfo; +import android.print.PrinterId; +import android.printservice.PrintServiceInfo; +import android.provider.Settings; +import android.text.TextUtils; +import android.util.SparseArray; + +import com.android.internal.R; +import com.android.internal.content.PackageMonitor; +import com.android.internal.os.BackgroundThread; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +public final class PrintManagerService extends IPrintManager.Stub { + + private static final char COMPONENT_NAME_SEPARATOR = ':'; + + private static final String EXTRA_PRINT_SERVICE_COMPONENT_NAME = + "EXTRA_PRINT_SERVICE_COMPONENT_NAME"; + + private final Object mLock = new Object(); + + private final Context mContext; + + private final SparseArray<UserState> mUserStates = new SparseArray<UserState>(); + + private int mCurrentUserId = UserHandle.USER_OWNER; + + public PrintManagerService(Context context) { + mContext = context; + registerContentObservers(); + registerBoradcastReceivers(); + } + + public void systemRuning() { + BackgroundThread.getHandler().post(new Runnable() { + @Override + public void run() { + final UserState userState; + synchronized (mLock) { + userState = getCurrentUserStateLocked(); + userState.updateIfNeededLocked(); + } + // This is the first time we switch to this user after boot, so + // now is the time to remove obsolete print jobs since they + // are from the last boot and no application would query them. + userState.removeObsoletePrintJobs(); + } + }); + } + + @Override + public Bundle print(String printJobName, IPrintDocumentAdapter adapter, + PrintAttributes attributes, String packageName, int appId, int userId) { + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + String resolvedPackageName = resolveCallingPackageNameEnforcingSecurity(packageName); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + return userState.print(printJobName, adapter, attributes, + resolvedPackageName, resolvedAppId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public List<PrintJobInfo> getPrintJobInfos(int appId, int userId) { + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + return userState.getPrintJobInfos(resolvedAppId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId, int userId) { + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + return userState.getPrintJobInfo(printJobId, resolvedAppId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void cancelPrintJob(PrintJobId printJobId, int appId, int userId) { + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.cancelPrintJob(printJobId, resolvedAppId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void restartPrintJob(PrintJobId printJobId, int appId, int userId) { + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.restartPrintJob(printJobId, resolvedAppId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public List<PrintServiceInfo> getEnabledPrintServices(int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + return userState.getEnabledPrintServices(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public List<PrintServiceInfo> getInstalledPrintServices(int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + return userState.getInstalledPrintServices(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void createPrinterDiscoverySession(IPrinterDiscoveryObserver observer, + int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.createPrinterDiscoverySession(observer); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void destroyPrinterDiscoverySession(IPrinterDiscoveryObserver observer, + int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.destroyPrinterDiscoverySession(observer); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void startPrinterDiscovery(IPrinterDiscoveryObserver observer, + List<PrinterId> priorityList, int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.startPrinterDiscovery(observer, priorityList); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void stopPrinterDiscovery(IPrinterDiscoveryObserver observer, int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.stopPrinterDiscovery(observer); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void validatePrinters(List<PrinterId> printerIds, int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.validatePrinters(printerIds); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void startPrinterStateTracking(PrinterId printerId, int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.startPrinterStateTracking(printerId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void stopPrinterStateTracking(PrinterId printerId, int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.stopPrinterStateTracking(printerId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void addPrintJobStateChangeListener(IPrintJobStateChangeListener listener, + int appId, int userId) throws RemoteException { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final int resolvedAppId = resolveCallingAppEnforcingPermissions(appId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.addPrintJobStateChangeListener(listener, resolvedAppId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void removePrintJobStateChangeListener(IPrintJobStateChangeListener listener, + int userId) { + final int resolvedUserId = resolveCallingUserEnforcingPermissions(userId); + final UserState userState; + synchronized (mLock) { + userState = getOrCreateUserStateLocked(resolvedUserId); + } + final long identity = Binder.clearCallingIdentity(); + try { + userState.removePrintJobStateChangeListener(listener); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + @Override + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + if (mContext.checkCallingOrSelfPermission(Manifest.permission.DUMP) + != PackageManager.PERMISSION_GRANTED) { + pw.println("Permission Denial: can't dump PrintManager from from pid=" + + Binder.getCallingPid() + + ", uid=" + Binder.getCallingUid()); + return; + } + + synchronized (mLock) { + pw.println("PRINT MANAGER STATE (dumpsys print)"); + final int userStateCount = mUserStates.size(); + for (int i = 0; i < userStateCount; i++) { + UserState userState = mUserStates.get(i); + userState.dump(fd, pw, ""); + pw.println(); + } + } + } + + private void registerContentObservers() { + final Uri enabledPrintServicesUri = Settings.Secure.getUriFor( + Settings.Secure.ENABLED_PRINT_SERVICES); + + ContentObserver observer = new ContentObserver(BackgroundThread.getHandler()) { + @Override + public void onChange(boolean selfChange, Uri uri) { + if (enabledPrintServicesUri.equals(uri)) { + synchronized (mLock) { + UserState userState = getCurrentUserStateLocked(); + userState.updateIfNeededLocked(); + } + } + } + }; + + mContext.getContentResolver().registerContentObserver(enabledPrintServicesUri, + false, observer, UserHandle.USER_ALL); + } + + private void registerBoradcastReceivers() { + PackageMonitor monitor = new PackageMonitor() { + @Override + public boolean onPackageChanged(String packageName, int uid, String[] components) { + synchronized (mLock) { + UserState userState = getOrCreateUserStateLocked(getChangingUserId()); + Iterator<ComponentName> iterator = userState.getEnabledServices().iterator(); + while (iterator.hasNext()) { + ComponentName componentName = iterator.next(); + if (packageName.equals(componentName.getPackageName())) { + userState.updateIfNeededLocked(); + return true; + } + } + } + return false; + } + + @Override + public void onPackageRemoved(String packageName, int uid) { + synchronized (mLock) { + UserState userState = getOrCreateUserStateLocked(getChangingUserId()); + Iterator<ComponentName> iterator = userState.getEnabledServices().iterator(); + while (iterator.hasNext()) { + ComponentName componentName = iterator.next(); + if (packageName.equals(componentName.getPackageName())) { + iterator.remove(); + persistComponentNamesToSettingLocked( + Settings.Secure.ENABLED_PRINT_SERVICES, + userState.getEnabledServices(), getChangingUserId()); + userState.updateIfNeededLocked(); + return; + } + } + } + } + + @Override + public boolean onHandleForceStop(Intent intent, String[] stoppedPackages, + int uid, boolean doit) { + synchronized (mLock) { + UserState userState = getOrCreateUserStateLocked(getChangingUserId()); + boolean stoppedSomePackages = false; + Iterator<ComponentName> iterator = userState.getEnabledServices().iterator(); + while (iterator.hasNext()) { + ComponentName componentName = iterator.next(); + String componentPackage = componentName.getPackageName(); + for (String stoppedPackage : stoppedPackages) { + if (componentPackage.equals(stoppedPackage)) { + if (!doit) { + return true; + } + stoppedSomePackages = true; + break; + } + } + } + if (stoppedSomePackages) { + userState.updateIfNeededLocked(); + } + return false; + } + } + + @Override + public void onPackageAdded(String packageName, int uid) { + Intent intent = new Intent(android.printservice.PrintService.SERVICE_INTERFACE); + intent.setPackage(packageName); + + List<ResolveInfo> installedServices = mContext.getPackageManager() + .queryIntentServicesAsUser(intent, PackageManager.GET_SERVICES, + getChangingUserId()); + + if (installedServices == null) { + return; + } + + final int installedServiceCount = installedServices.size(); + for (int i = 0; i < installedServiceCount; i++) { + ServiceInfo serviceInfo = installedServices.get(i).serviceInfo; + ComponentName component = new ComponentName(serviceInfo.packageName, + serviceInfo.name); + String label = serviceInfo.loadLabel(mContext.getPackageManager()).toString(); + showEnableInstalledPrintServiceNotification(component, label, + getChangingUserId()); + } + } + + private void persistComponentNamesToSettingLocked(String settingName, + Set<ComponentName> componentNames, int userId) { + StringBuilder builder = new StringBuilder(); + for (ComponentName componentName : componentNames) { + if (builder.length() > 0) { + builder.append(COMPONENT_NAME_SEPARATOR); + } + builder.append(componentName.flattenToShortString()); + } + Settings.Secure.putStringForUser(mContext.getContentResolver(), + settingName, builder.toString(), userId); + } + }; + + // package changes + monitor.register(mContext, BackgroundThread.getHandler().getLooper(), + UserHandle.ALL, true); + + // user changes + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_USER_SWITCHED); + intentFilter.addAction(Intent.ACTION_USER_REMOVED); + + mContext.registerReceiverAsUser(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + if (Intent.ACTION_USER_SWITCHED.equals(action)) { + switchUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, 0)); + } + } + }, UserHandle.ALL, intentFilter, null, BackgroundThread.getHandler()); + } + + private UserState getCurrentUserStateLocked() { + return getOrCreateUserStateLocked(mCurrentUserId); + } + + private UserState getOrCreateUserStateLocked(int userId) { + UserState userState = mUserStates.get(userId); + if (userState == null) { + userState = new UserState(mContext, userId, mLock); + mUserStates.put(userId, userState); + } + return userState; + } + + private void switchUser(int newUserId) { + UserState userState; + synchronized (mLock) { + if (newUserId == mCurrentUserId) { + return; + } + mCurrentUserId = newUserId; + userState = mUserStates.get(mCurrentUserId); + if (userState == null) { + userState = getCurrentUserStateLocked(); + userState.updateIfNeededLocked(); + } else { + userState.updateIfNeededLocked(); + } + } + // This is the first time we switch to this user after boot, so + // now is the time to remove obsolete print jobs since they + // are from the last boot and no application would query them. + userState.removeObsoletePrintJobs(); + } + + private void removeUser(int removedUserId) { + synchronized (mLock) { + UserState userState = mUserStates.get(removedUserId); + if (userState != null) { + userState.destroyLocked(); + mUserStates.remove(removedUserId); + } + } + } + + private int resolveCallingAppEnforcingPermissions(int appId) { + final int callingUid = Binder.getCallingUid(); + if (callingUid == 0 || callingUid == Process.SYSTEM_UID + || callingUid == Process.SHELL_UID) { + return appId; + } + final int callingAppId = UserHandle.getAppId(callingUid); + if (appId == callingAppId) { + return appId; + } + if (mContext.checkCallingPermission( + "com.android.printspooler.permission.ACCESS_ALL_PRINT_JOBS") + != PackageManager.PERMISSION_GRANTED) { + throw new SecurityException("Call from app " + callingAppId + " as app " + + appId + " without com.android.printspooler.permission" + + ".ACCESS_ALL_PRINT_JOBS"); + } + return appId; + } + + private int resolveCallingUserEnforcingPermissions(int userId) { + final int callingUid = Binder.getCallingUid(); + if (callingUid == 0 || callingUid == Process.SYSTEM_UID + || callingUid == Process.SHELL_UID) { + return userId; + } + final int callingUserId = UserHandle.getUserId(callingUid); + if (callingUserId == userId) { + return userId; + } + if (mContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS_FULL) + != PackageManager.PERMISSION_GRANTED + || mContext.checkCallingPermission(Manifest.permission.INTERACT_ACROSS_USERS) + != PackageManager.PERMISSION_GRANTED) { + if (userId == UserHandle.USER_CURRENT_OR_SELF) { + return callingUserId; + } + throw new SecurityException("Call from user " + callingUserId + " as user " + + userId + " without permission INTERACT_ACROSS_USERS or " + + "INTERACT_ACROSS_USERS_FULL not allowed."); + } + if (userId == UserHandle.USER_CURRENT || userId == UserHandle.USER_CURRENT_OR_SELF) { + return mCurrentUserId; + } + throw new IllegalArgumentException("Calling user can be changed to only " + + "UserHandle.USER_CURRENT or UserHandle.USER_CURRENT_OR_SELF."); + } + + private String resolveCallingPackageNameEnforcingSecurity(String packageName) { + if (TextUtils.isEmpty(packageName)) { + return null; + } + String[] packages = mContext.getPackageManager().getPackagesForUid( + Binder.getCallingUid()); + final int packageCount = packages.length; + for (int i = 0; i < packageCount; i++) { + if (packageName.equals(packages[i])) { + return packageName; + } + } + return null; + } + + private void showEnableInstalledPrintServiceNotification(ComponentName component, + String label, int userId) { + UserHandle userHandle = new UserHandle(userId); + + Intent intent = new Intent(Settings.ACTION_PRINT_SETTINGS); + intent.putExtra(EXTRA_PRINT_SERVICE_COMPONENT_NAME, component.flattenToString()); + + PendingIntent pendingIntent = PendingIntent.getActivityAsUser(mContext, 0, intent, + PendingIntent.FLAG_ONE_SHOT | PendingIntent.FLAG_CANCEL_CURRENT, null, userHandle); + + Notification.Builder builder = new Notification.Builder(mContext) + .setSmallIcon(R.drawable.ic_print) + .setContentTitle(mContext.getString(R.string.print_service_installed_title, label)) + .setContentText(mContext.getString(R.string.print_service_installed_message)) + .setContentIntent(pendingIntent) + .setWhen(System.currentTimeMillis()) + .setAutoCancel(true) + .setShowWhen(true); + + NotificationManager notificationManager = (NotificationManager) mContext + .getSystemService(Context.NOTIFICATION_SERVICE); + + String notificationTag = getClass().getName() + ":" + component.flattenToString(); + notificationManager.notifyAsUser(notificationTag, 0, builder.build(), + userHandle); + } +} diff --git a/services/java/com/android/server/print/RemotePrintService.java b/services/java/com/android/server/print/RemotePrintService.java new file mode 100644 index 000000000000..1bb61d246eb8 --- /dev/null +++ b/services/java/com/android/server/print/RemotePrintService.java @@ -0,0 +1,806 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.print; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.content.pm.ParceledListSlice; +import android.os.Binder; +import android.os.Handler; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.Looper; +import android.os.Message; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.UserHandle; +import android.print.PrintJobId; +import android.print.PrintJobInfo; +import android.print.PrintManager; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.printservice.IPrintService; +import android.printservice.IPrintServiceClient; +import android.util.Slog; + +import java.io.PrintWriter; +import java.lang.ref.WeakReference; +import java.util.ArrayList; +import java.util.List; + +/** + * This class represents a remote print service. It abstracts away the binding + * and unbinding from the remote implementation. Clients can call methods of + * this class without worrying about when and how to bind/unbind. + */ +final class RemotePrintService implements DeathRecipient { + + private static final String LOG_TAG = "RemotePrintService"; + + private static final boolean DEBUG = false; + + private final Context mContext; + + private final ComponentName mComponentName; + + private final Intent mIntent; + + private final RemotePrintSpooler mSpooler; + + private final PrintServiceCallbacks mCallbacks; + + private final int mUserId; + + private final List<Runnable> mPendingCommands = new ArrayList<Runnable>(); + + private final ServiceConnection mServiceConnection = new RemoteServiceConneciton(); + + private final RemotePrintServiceClient mPrintServiceClient; + + private final Handler mHandler; + + private IPrintService mPrintService; + + private boolean mBinding; + + private boolean mDestroyed; + + private boolean mHasActivePrintJobs; + + private boolean mHasPrinterDiscoverySession; + + private boolean mServiceDied; + + private List<PrinterId> mDiscoveryPriorityList; + + private List<PrinterId> mTrackedPrinterList; + + public static interface PrintServiceCallbacks { + public void onPrintersAdded(List<PrinterInfo> printers); + public void onPrintersRemoved(List<PrinterId> printerIds); + public void onServiceDied(RemotePrintService service); + } + + public RemotePrintService(Context context, ComponentName componentName, int userId, + RemotePrintSpooler spooler, PrintServiceCallbacks callbacks) { + mContext = context; + mCallbacks = callbacks; + mComponentName = componentName; + mIntent = new Intent().setComponent(mComponentName); + mUserId = userId; + mSpooler = spooler; + mHandler = new MyHandler(context.getMainLooper()); + mPrintServiceClient = new RemotePrintServiceClient(this); + } + + public ComponentName getComponentName() { + return mComponentName; + } + + public void destroy() { + mHandler.sendEmptyMessage(MyHandler.MSG_DESTROY); + } + + private void handleDestroy() { + throwIfDestroyed(); + + // Stop tracking printers. + if (mTrackedPrinterList != null) { + final int trackedPrinterCount = mTrackedPrinterList.size(); + for (int i = 0; i < trackedPrinterCount; i++) { + PrinterId printerId = mTrackedPrinterList.get(i); + if (printerId.getServiceName().equals(mComponentName)) { + handleStopPrinterStateTracking(printerId); + } + } + } + + // Stop printer discovery. + if (mDiscoveryPriorityList != null) { + handleStopPrinterDiscovery(); + } + + // Destroy the discovery session. + if (mHasPrinterDiscoverySession) { + handleDestroyPrinterDiscoverySession(); + } + + // Unbind. + ensureUnbound(); + + // Done + mDestroyed = true; + } + + @Override + public void binderDied() { + mHandler.sendEmptyMessage(MyHandler.MSG_BINDER_DIED); + } + + private void handleBinderDied() { + mPrintService.asBinder().unlinkToDeath(this, 0); + mPrintService = null; + mServiceDied = true; + mCallbacks.onServiceDied(this); + } + + public void onAllPrintJobsHandled() { + mHandler.sendEmptyMessage(MyHandler.MSG_ON_ALL_PRINT_JOBS_HANDLED); + } + + private void handleOnAllPrintJobsHandled() { + throwIfDestroyed(); + mHasActivePrintJobs = false; + if (!isBound()) { + // The service is dead and neither has active jobs nor discovery + // session, so ensure we are unbound since the service has no work. + if (mServiceDied && !mHasPrinterDiscoverySession) { + ensureUnbound(); + return; + } + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleOnAllPrintJobsHandled(); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] onAllPrintJobsHandled()"); + } + // If the service has a printer discovery session + // created we should not disconnect from it just yet. + if (!mHasPrinterDiscoverySession) { + ensureUnbound(); + } + } + } + + public void onRequestCancelPrintJob(PrintJobInfo printJob) { + mHandler.obtainMessage(MyHandler.MSG_ON_REQUEST_CANCEL_PRINT_JOB, + printJob).sendToTarget(); + } + + private void handleRequestCancelPrintJob(final PrintJobInfo printJob) { + throwIfDestroyed(); + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleRequestCancelPrintJob(printJob); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] requestCancelPrintJob()"); + } + try { + mPrintService.requestCancelPrintJob(printJob); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error canceling a pring job.", re); + } + } + } + + public void onPrintJobQueued(PrintJobInfo printJob) { + mHandler.obtainMessage(MyHandler.MSG_ON_PRINT_JOB_QUEUED, + printJob).sendToTarget(); + } + + private void handleOnPrintJobQueued(final PrintJobInfo printJob) { + throwIfDestroyed(); + mHasActivePrintJobs = true; + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleOnPrintJobQueued(printJob); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] onPrintJobQueued()"); + } + try { + mPrintService.onPrintJobQueued(printJob); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error announcing queued pring job.", re); + } + } + } + + public void createPrinterDiscoverySession() { + mHandler.sendEmptyMessage(MyHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION); + } + + private void handleCreatePrinterDiscoverySession() { + throwIfDestroyed(); + mHasPrinterDiscoverySession = true; + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleCreatePrinterDiscoverySession(); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] createPrinterDiscoverySession()"); + } + try { + mPrintService.createPrinterDiscoverySession(); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error creating printer dicovery session.", re); + } + } + } + + public void destroyPrinterDiscoverySession() { + mHandler.sendEmptyMessage(MyHandler.MSG_DESTROY_PRINTER_DISCOVERY_SESSION); + } + + private void handleDestroyPrinterDiscoverySession() { + throwIfDestroyed(); + mHasPrinterDiscoverySession = false; + if (!isBound()) { + // The service is dead and neither has active jobs nor discovery + // session, so ensure we are unbound since the service has no work. + if (mServiceDied && !mHasActivePrintJobs) { + ensureUnbound(); + return; + } + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleDestroyPrinterDiscoverySession(); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] destroyPrinterDiscoverySession()"); + } + try { + mPrintService.destroyPrinterDiscoverySession(); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error destroying printer dicovery session.", re); + } + // If the service has no print jobs and no active discovery + // session anymore we should disconnect from it. + if (!mHasActivePrintJobs) { + ensureUnbound(); + } + } + } + + public void startPrinterDiscovery(List<PrinterId> priorityList) { + mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_DISCOVERY, + priorityList).sendToTarget(); + } + + private void handleStartPrinterDiscovery(final List<PrinterId> priorityList) { + throwIfDestroyed(); + // Take a note that we are doing discovery. + mDiscoveryPriorityList = new ArrayList<PrinterId>(); + if (priorityList != null) { + mDiscoveryPriorityList.addAll(priorityList); + } + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleStartPrinterDiscovery(priorityList); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] startPrinterDiscovery()"); + } + try { + mPrintService.startPrinterDiscovery(priorityList); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error starting printer dicovery.", re); + } + } + } + + public void stopPrinterDiscovery() { + mHandler.sendEmptyMessage(MyHandler.MSG_STOP_PRINTER_DISCOVERY); + } + + private void handleStopPrinterDiscovery() { + throwIfDestroyed(); + // We are not doing discovery anymore. + mDiscoveryPriorityList = null; + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleStopPrinterDiscovery(); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] stopPrinterDiscovery()"); + } + try { + mPrintService.stopPrinterDiscovery(); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error stopping printer dicovery.", re); + } + } + } + + public void validatePrinters(List<PrinterId> printerIds) { + mHandler.obtainMessage(MyHandler.MSG_VALIDATE_PRINTERS, + printerIds).sendToTarget(); + } + + private void handleValidatePrinters(final List<PrinterId> printerIds) { + throwIfDestroyed(); + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleValidatePrinters(printerIds); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] validatePrinters()"); + } + try { + mPrintService.validatePrinters(printerIds); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error requesting printers validation.", re); + } + } + } + + public void startPrinterStateTracking(PrinterId printerId) { + mHandler.obtainMessage(MyHandler.MSG_START_PRINTER_STATE_TRACKING, + printerId).sendToTarget(); + } + + private void handleStartPrinterStateTracking(final PrinterId printerId) { + throwIfDestroyed(); + // Take a note we are tracking the printer. + if (mTrackedPrinterList == null) { + mTrackedPrinterList = new ArrayList<PrinterId>(); + } + mTrackedPrinterList.add(printerId); + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleStartPrinterStateTracking(printerId); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] startPrinterTracking()"); + } + try { + mPrintService.startPrinterStateTracking(printerId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error requesting start printer tracking.", re); + } + } + } + + public void stopPrinterStateTracking(PrinterId printerId) { + mHandler.obtainMessage(MyHandler.MSG_STOP_PRINTER_STATE_TRACKING, + printerId).sendToTarget(); + } + + private void handleStopPrinterStateTracking(final PrinterId printerId) { + throwIfDestroyed(); + // We are no longer tracking the printer. + if (mTrackedPrinterList == null || !mTrackedPrinterList.remove(printerId)) { + return; + } + if (mTrackedPrinterList.isEmpty()) { + mTrackedPrinterList = null; + } + if (!isBound()) { + ensureBound(); + mPendingCommands.add(new Runnable() { + @Override + public void run() { + handleStopPrinterStateTracking(printerId); + } + }); + } else { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] stopPrinterTracking()"); + } + try { + mPrintService.stopPrinterStateTracking(printerId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error requesting stop printer tracking.", re); + } + } + } + + public void dump(PrintWriter pw, String prefix) { + String tab = " "; + pw.append(prefix).append("service:").println(); + pw.append(prefix).append(tab).append("componentName=") + .append(mComponentName.flattenToString()).println(); + pw.append(prefix).append(tab).append("destroyed=") + .append(String.valueOf(mDestroyed)).println(); + pw.append(prefix).append(tab).append("bound=") + .append(String.valueOf(isBound())).println(); + pw.append(prefix).append(tab).append("hasDicoverySession=") + .append(String.valueOf(mHasPrinterDiscoverySession)).println(); + pw.append(prefix).append(tab).append("hasActivePrintJobs=") + .append(String.valueOf(mHasActivePrintJobs)).println(); + pw.append(prefix).append(tab).append("isDiscoveringPrinters=") + .append(String.valueOf(mDiscoveryPriorityList != null)).println(); + pw.append(prefix).append(tab).append("trackedPrinters=") + .append((mTrackedPrinterList != null) ? mTrackedPrinterList.toString() : "null"); + } + + private boolean isBound() { + return mPrintService != null; + } + + private void ensureBound() { + if (isBound() || mBinding) { + return; + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureBound()"); + } + mBinding = true; + mContext.bindServiceAsUser(mIntent, mServiceConnection, + Context.BIND_AUTO_CREATE, new UserHandle(mUserId)); + } + + private void ensureUnbound() { + if (!isBound() && !mBinding) { + return; + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserId + "] ensureUnbound()"); + } + mBinding = false; + mPendingCommands.clear(); + mHasActivePrintJobs = false; + mHasPrinterDiscoverySession = false; + mDiscoveryPriorityList = null; + mTrackedPrinterList = null; + if (isBound()) { + try { + mPrintService.setClient(null); + } catch (RemoteException re) { + /* ignore */ + } + mPrintService.asBinder().unlinkToDeath(this, 0); + mPrintService = null; + mContext.unbindService(mServiceConnection); + } + } + + private void throwIfDestroyed() { + if (mDestroyed) { + throw new IllegalStateException("Cannot interact with a destroyed service"); + } + } + + private class RemoteServiceConneciton implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + if (mDestroyed || !mBinding) { + mContext.unbindService(mServiceConnection); + return; + } + mBinding = false; + mPrintService = IPrintService.Stub.asInterface(service); + try { + service.linkToDeath(RemotePrintService.this, 0); + } catch (RemoteException re) { + handleBinderDied(); + return; + } + try { + mPrintService.setClient(mPrintServiceClient); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error setting client for: " + service, re); + handleBinderDied(); + return; + } + // If the service died and there is a discovery session, recreate it. + if (mServiceDied && mHasPrinterDiscoverySession) { + handleCreatePrinterDiscoverySession(); + } + // If the service died and there is discovery started, restart it. + if (mServiceDied && mDiscoveryPriorityList != null) { + handleStartPrinterDiscovery(mDiscoveryPriorityList); + } + // If the service died and printers were tracked, start tracking. + if (mServiceDied && mTrackedPrinterList != null) { + final int trackedPrinterCount = mTrackedPrinterList.size(); + for (int i = 0; i < trackedPrinterCount; i++) { + handleStartPrinterStateTracking(mTrackedPrinterList.get(i)); + } + } + // Finally, do all the pending work. + while (!mPendingCommands.isEmpty()) { + Runnable pendingCommand = mPendingCommands.remove(0); + pendingCommand.run(); + } + // We did a best effort to get to the last state if we crashed. + // If we do not have print jobs and no discovery is in progress, + // then no need to be bound. + if (!mHasPrinterDiscoverySession && !mHasActivePrintJobs) { + ensureUnbound(); + } + mServiceDied = false; + } + + @Override + public void onServiceDisconnected(ComponentName name) { + mBinding = true; + } + } + + private final class MyHandler extends Handler { + public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 1; + public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 2; + public static final int MSG_START_PRINTER_DISCOVERY = 3; + public static final int MSG_STOP_PRINTER_DISCOVERY = 4; + public static final int MSG_VALIDATE_PRINTERS = 5; + public static final int MSG_START_PRINTER_STATE_TRACKING = 6; + public static final int MSG_STOP_PRINTER_STATE_TRACKING = 7; + public static final int MSG_ON_ALL_PRINT_JOBS_HANDLED = 8; + public static final int MSG_ON_REQUEST_CANCEL_PRINT_JOB = 9; + public static final int MSG_ON_PRINT_JOB_QUEUED = 10; + public static final int MSG_DESTROY = 11; + public static final int MSG_BINDER_DIED = 12; + + public MyHandler(Looper looper) { + super(looper, null, false); + } + + @Override + @SuppressWarnings("unchecked") + public void handleMessage(Message message) { + switch (message.what) { + case MSG_CREATE_PRINTER_DISCOVERY_SESSION: { + handleCreatePrinterDiscoverySession(); + } break; + + case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: { + handleDestroyPrinterDiscoverySession(); + } break; + + case MSG_START_PRINTER_DISCOVERY: { + List<PrinterId> priorityList = (ArrayList<PrinterId>) message.obj; + handleStartPrinterDiscovery(priorityList); + } break; + + case MSG_STOP_PRINTER_DISCOVERY: { + handleStopPrinterDiscovery(); + } break; + + case MSG_VALIDATE_PRINTERS: { + List<PrinterId> printerIds = (List<PrinterId>) message.obj; + handleValidatePrinters(printerIds); + } break; + + case MSG_START_PRINTER_STATE_TRACKING: { + PrinterId printerId = (PrinterId) message.obj; + handleStartPrinterStateTracking(printerId); + } break; + + case MSG_STOP_PRINTER_STATE_TRACKING: { + PrinterId printerId = (PrinterId) message.obj; + handleStopPrinterStateTracking(printerId); + } break; + + case MSG_ON_ALL_PRINT_JOBS_HANDLED: { + handleOnAllPrintJobsHandled(); + } break; + + case MSG_ON_REQUEST_CANCEL_PRINT_JOB: { + PrintJobInfo printJob = (PrintJobInfo) message.obj; + handleRequestCancelPrintJob(printJob); + } break; + + case MSG_ON_PRINT_JOB_QUEUED: { + PrintJobInfo printJob = (PrintJobInfo) message.obj; + handleOnPrintJobQueued(printJob); + } break; + + case MSG_DESTROY: { + handleDestroy(); + } break; + + case MSG_BINDER_DIED: { + handleBinderDied(); + } break; + } + } + } + + private static final class RemotePrintServiceClient extends IPrintServiceClient.Stub { + private final WeakReference<RemotePrintService> mWeakService; + + public RemotePrintServiceClient(RemotePrintService service) { + mWeakService = new WeakReference<RemotePrintService>(service); + } + + @Override + public List<PrintJobInfo> getPrintJobInfos() { + RemotePrintService service = mWeakService.get(); + if (service != null) { + final long identity = Binder.clearCallingIdentity(); + try { + return service.mSpooler.getPrintJobInfos(service.mComponentName, + PrintJobInfo.STATE_ANY_SCHEDULED, PrintManager.APP_ID_ANY); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return null; + } + + @Override + public PrintJobInfo getPrintJobInfo(PrintJobId printJobId) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + final long identity = Binder.clearCallingIdentity(); + try { + return service.mSpooler.getPrintJobInfo(printJobId, + PrintManager.APP_ID_ANY); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return null; + } + + @Override + public boolean setPrintJobState(PrintJobId printJobId, int state, String error) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + final long identity = Binder.clearCallingIdentity(); + try { + return service.mSpooler.setPrintJobState(printJobId, state, error); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return false; + } + + @Override + public boolean setPrintJobTag(PrintJobId printJobId, String tag) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + final long identity = Binder.clearCallingIdentity(); + try { + return service.mSpooler.setPrintJobTag(printJobId, tag); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + return false; + } + + @Override + public void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + final long identity = Binder.clearCallingIdentity(); + try { + service.mSpooler.writePrintJobData(fd, printJobId); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + public void onPrintersAdded(ParceledListSlice printers) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + List<PrinterInfo> addedPrinters = (List<PrinterInfo>) printers.getList(); + throwIfPrinterIdsForPrinterInfoTampered(service.mComponentName, addedPrinters); + final long identity = Binder.clearCallingIdentity(); + try { + service.mCallbacks.onPrintersAdded(addedPrinters); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @Override + @SuppressWarnings({"rawtypes", "unchecked"}) + public void onPrintersRemoved(ParceledListSlice printerIds) { + RemotePrintService service = mWeakService.get(); + if (service != null) { + List<PrinterId> removedPrinterIds = (List<PrinterId>) printerIds.getList(); + throwIfPrinterIdsTampered(service.mComponentName, removedPrinterIds); + final long identity = Binder.clearCallingIdentity(); + try { + service.mCallbacks.onPrintersRemoved(removedPrinterIds); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + private void throwIfPrinterIdsForPrinterInfoTampered(ComponentName serviceName, + List<PrinterInfo> printerInfos) { + final int printerInfoCount = printerInfos.size(); + for (int i = 0; i < printerInfoCount; i++) { + PrinterId printerId = printerInfos.get(i).getId(); + throwIfPrinterIdTampered(serviceName, printerId); + } + } + + private void throwIfPrinterIdsTampered(ComponentName serviceName, + List<PrinterId> printerIds) { + final int printerIdCount = printerIds.size(); + for (int i = 0; i < printerIdCount; i++) { + PrinterId printerId = printerIds.get(i); + throwIfPrinterIdTampered(serviceName, printerId); + } + } + + private void throwIfPrinterIdTampered(ComponentName serviceName, PrinterId printerId) { + if (printerId == null || printerId.getServiceName() == null + || !printerId.getServiceName().equals(serviceName)) { + throw new IllegalArgumentException("Invalid printer id: " + printerId); + } + } + } +} diff --git a/services/java/com/android/server/print/RemotePrintSpooler.java b/services/java/com/android/server/print/RemotePrintSpooler.java new file mode 100644 index 000000000000..ffe9806a2418 --- /dev/null +++ b/services/java/com/android/server/print/RemotePrintSpooler.java @@ -0,0 +1,634 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.print; + +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.ServiceConnection; +import android.os.Binder; +import android.os.IBinder; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.SystemClock; +import android.os.UserHandle; +import android.print.IPrintSpooler; +import android.print.IPrintSpoolerCallbacks; +import android.print.IPrintSpoolerClient; +import android.print.PrintJobId; +import android.print.PrintJobInfo; +import android.util.Slog; +import android.util.TimedRemoteCaller; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.lang.ref.WeakReference; +import java.util.List; +import java.util.concurrent.TimeoutException; + +import libcore.io.IoUtils; + +/** + * This represents the remote print spooler as a local object to the + * PrintManagerSerivce. It is responsible to connecting to the remote + * spooler if needed, to make the timed remote calls, to handle + * remote exceptions, and to bind/unbind to the remote instance as + * needed. + */ +final class RemotePrintSpooler { + + private static final String LOG_TAG = "RemotePrintSpooler"; + + private static final boolean DEBUG = false; + + private static final long BIND_SPOOLER_SERVICE_TIMEOUT = 10000; + + private final Object mLock = new Object(); + + private final GetPrintJobInfosCaller mGetPrintJobInfosCaller = new GetPrintJobInfosCaller(); + + private final GetPrintJobInfoCaller mGetPrintJobInfoCaller = new GetPrintJobInfoCaller(); + + private final SetPrintJobStateCaller mSetPrintJobStatusCaller = new SetPrintJobStateCaller(); + + private final SetPrintJobTagCaller mSetPrintJobTagCaller = new SetPrintJobTagCaller(); + + private final ServiceConnection mServiceConnection = new MyServiceConnection(); + + private final Context mContext; + + private final UserHandle mUserHandle; + + private final PrintSpoolerClient mClient; + + private final Intent mIntent; + + private final PrintSpoolerCallbacks mCallbacks; + + private IPrintSpooler mRemoteInstance; + + private boolean mDestroyed; + + private boolean mCanUnbind; + + public static interface PrintSpoolerCallbacks { + public void onPrintJobQueued(PrintJobInfo printJob); + public void onAllPrintJobsForServiceHandled(ComponentName printService); + public void onPrintJobStateChanged(PrintJobInfo printJob); + } + + public RemotePrintSpooler(Context context, int userId, + PrintSpoolerCallbacks callbacks) { + mContext = context; + mUserHandle = new UserHandle(userId); + mCallbacks = callbacks; + mClient = new PrintSpoolerClient(this); + mIntent = new Intent(); + mIntent.setComponent(new ComponentName("com.android.printspooler", + "com.android.printspooler.PrintSpoolerService")); + } + + public final List<PrintJobInfo> getPrintJobInfos(ComponentName componentName, int state, + int appId) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + return mGetPrintJobInfosCaller.getPrintJobInfos(getRemoteInstanceLazy(), + componentName, state, appId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error getting print jobs.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error getting print jobs.", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfos()"); + } + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + return null; + } + + public final void createPrintJob(PrintJobInfo printJob) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + getRemoteInstanceLazy().createPrintJob(printJob); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error creating print job.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error creating print job.", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] createPrintJob()"); + } + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + } + + public final void writePrintJobData(ParcelFileDescriptor fd, PrintJobId printJobId) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + getRemoteInstanceLazy().writePrintJobData(fd, printJobId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error writing print job data.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error writing print job data.", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] writePrintJobData()"); + } + // We passed the file descriptor across and now the other + // side is responsible to close it, so close the local copy. + IoUtils.closeQuietly(fd); + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + } + + public final PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + return mGetPrintJobInfoCaller.getPrintJobInfo(getRemoteInstanceLazy(), + printJobId, appId); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error getting print job info.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error getting print job info.", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] getPrintJobInfo()"); + } + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + return null; + } + + public final boolean setPrintJobState(PrintJobId printJobId, int state, String error) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + return mSetPrintJobStatusCaller.setPrintJobState(getRemoteInstanceLazy(), + printJobId, state, error); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error setting print job state.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error setting print job state.", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobState()"); + } + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + return false; + } + + public final boolean setPrintJobTag(PrintJobId printJobId, String tag) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + return mSetPrintJobTagCaller.setPrintJobTag(getRemoteInstanceLazy(), + printJobId, tag); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error setting print job tag.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error setting print job tag.", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] setPrintJobTag()"); + } + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + return false; + } + + public final void setPrintJobCancelling(PrintJobId printJobId, boolean cancelling) { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + getRemoteInstanceLazy().setPrintJobCancelling(printJobId, + cancelling); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error setting print job cancelling.", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error setting print job cancelling.", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + + "] setPrintJobCancelling()"); + } + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + } + + public final void removeObsoletePrintJobs() { + throwIfCalledOnMainThread(); + synchronized (mLock) { + throwIfDestroyedLocked(); + mCanUnbind = false; + } + try { + getRemoteInstanceLazy().removeObsoletePrintJobs(); + } catch (RemoteException re) { + Slog.e(LOG_TAG, "Error removing obsolete print jobs .", re); + } catch (TimeoutException te) { + Slog.e(LOG_TAG, "Error removing obsolete print jobs .", te); + } finally { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + + "] removeObsoletePrintJobs()"); + } + synchronized (mLock) { + mCanUnbind = true; + mLock.notifyAll(); + } + } + } + + public final void destroy() { + throwIfCalledOnMainThread(); + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] destroy()"); + } + synchronized (mLock) { + throwIfDestroyedLocked(); + unbindLocked(); + mDestroyed = true; + mCanUnbind = false; + } + } + + public void dump(FileDescriptor fd, PrintWriter pw, String prefix) { + synchronized (mLock) { + pw.append(prefix).append("destroyed=") + .append(String.valueOf(mDestroyed)).println(); + pw.append(prefix).append("bound=") + .append((mRemoteInstance != null) ? "true" : "false").println(); + + pw.flush(); + + try { + getRemoteInstanceLazy().asBinder().dump(fd, new String[]{prefix}); + } catch (TimeoutException te) { + /* ignore */ + } catch (RemoteException re) { + /* ignore */ + } + } + } + + private void onAllPrintJobsHandled() { + synchronized (mLock) { + throwIfDestroyedLocked(); + unbindLocked(); + } + } + + private void onPrintJobStateChanged(PrintJobInfo printJob) { + mCallbacks.onPrintJobStateChanged(printJob); + } + + private IPrintSpooler getRemoteInstanceLazy() throws TimeoutException { + synchronized (mLock) { + if (mRemoteInstance != null) { + return mRemoteInstance; + } + bindLocked(); + return mRemoteInstance; + } + } + + private void bindLocked() throws TimeoutException { + if (mRemoteInstance != null) { + return; + } + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] bindLocked()"); + } + + mContext.bindServiceAsUser(mIntent, mServiceConnection, + Context.BIND_AUTO_CREATE, mUserHandle); + + final long startMillis = SystemClock.uptimeMillis(); + while (true) { + if (mRemoteInstance != null) { + break; + } + final long elapsedMillis = SystemClock.uptimeMillis() - startMillis; + final long remainingMillis = BIND_SPOOLER_SERVICE_TIMEOUT - elapsedMillis; + if (remainingMillis <= 0) { + throw new TimeoutException("Cannot get spooler!"); + } + try { + mLock.wait(remainingMillis); + } catch (InterruptedException ie) { + /* ignore */ + } + } + + mCanUnbind = true; + mLock.notifyAll(); + } + + private void unbindLocked() { + if (mRemoteInstance == null) { + return; + } + while (true) { + if (mCanUnbind) { + if (DEBUG) { + Slog.i(LOG_TAG, "[user: " + mUserHandle.getIdentifier() + "] unbindLocked()"); + } + clearClientLocked(); + mRemoteInstance = null; + mContext.unbindService(mServiceConnection); + return; + } + try { + mLock.wait(); + } catch (InterruptedException ie) { + /* ignore */ + } + } + + } + + private void setClientLocked() { + try { + mRemoteInstance.setClient(mClient); + } catch (RemoteException re) { + Slog.d(LOG_TAG, "Error setting print spooler client", re); + } + } + + private void clearClientLocked() { + try { + mRemoteInstance.setClient(null); + } catch (RemoteException re) { + Slog.d(LOG_TAG, "Error clearing print spooler client", re); + } + + } + + private void throwIfDestroyedLocked() { + if (mDestroyed) { + throw new IllegalStateException("Cannot interact with a destroyed instance."); + } + } + + private void throwIfCalledOnMainThread() { + if (Thread.currentThread() == mContext.getMainLooper().getThread()) { + throw new RuntimeException("Cannot invoke on the main thread"); + } + } + + private final class MyServiceConnection implements ServiceConnection { + @Override + public void onServiceConnected(ComponentName name, IBinder service) { + synchronized (mLock) { + mRemoteInstance = IPrintSpooler.Stub.asInterface(service); + setClientLocked(); + mLock.notifyAll(); + } + } + + @Override + public void onServiceDisconnected(ComponentName name) { + synchronized (mLock) { + clearClientLocked(); + mRemoteInstance = null; + } + } + } + + private static final class GetPrintJobInfosCaller + extends TimedRemoteCaller<List<PrintJobInfo>> { + private final IPrintSpoolerCallbacks mCallback; + + public GetPrintJobInfosCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new BasePrintSpoolerServiceCallbacks() { + @Override + public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobs, int sequence) { + onRemoteMethodResult(printJobs, sequence); + } + }; + } + + public List<PrintJobInfo> getPrintJobInfos(IPrintSpooler target, + ComponentName componentName, int state, int appId) + throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.getPrintJobInfos(mCallback, componentName, state, appId, sequence); + return getResultTimed(sequence); + } + } + + private static final class GetPrintJobInfoCaller extends TimedRemoteCaller<PrintJobInfo> { + private final IPrintSpoolerCallbacks mCallback; + + public GetPrintJobInfoCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new BasePrintSpoolerServiceCallbacks() { + @Override + public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) { + onRemoteMethodResult(printJob, sequence); + } + }; + } + + public PrintJobInfo getPrintJobInfo(IPrintSpooler target, PrintJobId printJobId, + int appId) throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.getPrintJobInfo(printJobId, mCallback, appId, sequence); + return getResultTimed(sequence); + } + } + + private static final class SetPrintJobStateCaller extends TimedRemoteCaller<Boolean> { + private final IPrintSpoolerCallbacks mCallback; + + public SetPrintJobStateCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new BasePrintSpoolerServiceCallbacks() { + @Override + public void onSetPrintJobStateResult(boolean success, int sequence) { + onRemoteMethodResult(success, sequence); + } + }; + } + + public boolean setPrintJobState(IPrintSpooler target, PrintJobId printJobId, + int status, String error) throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.setPrintJobState(printJobId, status, error, mCallback, sequence); + return getResultTimed(sequence); + } + } + + private static final class SetPrintJobTagCaller extends TimedRemoteCaller<Boolean> { + private final IPrintSpoolerCallbacks mCallback; + + public SetPrintJobTagCaller() { + super(TimedRemoteCaller.DEFAULT_CALL_TIMEOUT_MILLIS); + mCallback = new BasePrintSpoolerServiceCallbacks() { + @Override + public void onSetPrintJobTagResult(boolean success, int sequence) { + onRemoteMethodResult(success, sequence); + } + }; + } + + public boolean setPrintJobTag(IPrintSpooler target, PrintJobId printJobId, + String tag) throws RemoteException, TimeoutException { + final int sequence = onBeforeRemoteCall(); + target.setPrintJobTag(printJobId, tag, mCallback, sequence); + return getResultTimed(sequence); + } + } + + private static abstract class BasePrintSpoolerServiceCallbacks + extends IPrintSpoolerCallbacks.Stub { + @Override + public void onGetPrintJobInfosResult(List<PrintJobInfo> printJobIds, int sequence) { + /* do nothing */ + } + + @Override + public void onGetPrintJobInfoResult(PrintJobInfo printJob, int sequence) { + /* do nothing */ + } + + @Override + public void onCancelPrintJobResult(boolean canceled, int sequence) { + /* do nothing */ + } + + @Override + public void onSetPrintJobStateResult(boolean success, int sequece) { + /* do nothing */ + } + + @Override + public void onSetPrintJobTagResult(boolean success, int sequence) { + /* do nothing */ + } + } + + private static final class PrintSpoolerClient extends IPrintSpoolerClient.Stub { + + private final WeakReference<RemotePrintSpooler> mWeakSpooler; + + public PrintSpoolerClient(RemotePrintSpooler spooler) { + mWeakSpooler = new WeakReference<RemotePrintSpooler>(spooler); + } + + @Override + public void onPrintJobQueued(PrintJobInfo printJob) { + RemotePrintSpooler spooler = mWeakSpooler.get(); + if (spooler != null) { + final long identity = Binder.clearCallingIdentity(); + try { + spooler.mCallbacks.onPrintJobQueued(printJob); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @Override + public void onAllPrintJobsForServiceHandled(ComponentName printService) { + RemotePrintSpooler spooler = mWeakSpooler.get(); + if (spooler != null) { + final long identity = Binder.clearCallingIdentity(); + try { + spooler.mCallbacks.onAllPrintJobsForServiceHandled(printService); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @Override + public void onAllPrintJobsHandled() { + RemotePrintSpooler spooler = mWeakSpooler.get(); + if (spooler != null) { + final long identity = Binder.clearCallingIdentity(); + try { + spooler.onAllPrintJobsHandled(); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + + @Override + public void onPrintJobStateChanged(PrintJobInfo printJob) { + RemotePrintSpooler spooler = mWeakSpooler.get(); + if (spooler != null) { + final long identity = Binder.clearCallingIdentity(); + try { + spooler.onPrintJobStateChanged(printJob); + } finally { + Binder.restoreCallingIdentity(identity); + } + } + } + } +} diff --git a/services/java/com/android/server/print/UserState.java b/services/java/com/android/server/print/UserState.java new file mode 100644 index 000000000000..b69dceec55b0 --- /dev/null +++ b/services/java/com/android/server/print/UserState.java @@ -0,0 +1,1620 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.print; + +import android.app.PendingIntent; +import android.content.ComponentName; +import android.content.Context; +import android.content.Intent; +import android.content.IntentSender; +import android.content.pm.ApplicationInfo; +import android.content.pm.PackageManager; +import android.content.pm.ParceledListSlice; +import android.content.pm.ResolveInfo; +import android.content.pm.ServiceInfo; +import android.net.Uri; +import android.os.AsyncTask; +import android.os.Binder; +import android.os.Bundle; +import android.os.Handler; +import android.os.IBinder; +import android.os.IBinder.DeathRecipient; +import android.os.Looper; +import android.os.Message; +import android.os.RemoteCallbackList; +import android.os.RemoteException; +import android.os.UserHandle; +import android.print.IPrintDocumentAdapter; +import android.print.IPrintJobStateChangeListener; +import android.print.IPrinterDiscoveryObserver; +import android.print.PrintAttributes; +import android.print.PrintJobId; +import android.print.PrintJobInfo; +import android.print.PrintManager; +import android.print.PrinterId; +import android.print.PrinterInfo; +import android.printservice.PrintServiceInfo; +import android.provider.DocumentsContract; +import android.provider.Settings; +import android.text.TextUtils; +import android.text.TextUtils.SimpleStringSplitter; +import android.util.ArrayMap; +import android.util.ArraySet; +import android.util.Log; +import android.util.Slog; +import android.util.SparseArray; + +import com.android.internal.R; +import com.android.internal.os.BackgroundThread; +import com.android.internal.os.SomeArgs; +import com.android.server.print.RemotePrintService.PrintServiceCallbacks; +import com.android.server.print.RemotePrintSpooler.PrintSpoolerCallbacks; + +import java.io.FileDescriptor; +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +/** + * Represents the print state for a user. + */ +final class UserState implements PrintSpoolerCallbacks, PrintServiceCallbacks { + + private static final String LOG_TAG = "UserState"; + + private static final boolean DEBUG = false; + + private static final char COMPONENT_NAME_SEPARATOR = ':'; + + private final SimpleStringSplitter mStringColonSplitter = + new SimpleStringSplitter(COMPONENT_NAME_SEPARATOR); + + private final Intent mQueryIntent = + new Intent(android.printservice.PrintService.SERVICE_INTERFACE); + + private final ArrayMap<ComponentName, RemotePrintService> mActiveServices = + new ArrayMap<ComponentName, RemotePrintService>(); + + private final List<PrintServiceInfo> mInstalledServices = + new ArrayList<PrintServiceInfo>(); + + private final Set<ComponentName> mEnabledServices = + new ArraySet<ComponentName>(); + + private final PrintJobForAppCache mPrintJobForAppCache = + new PrintJobForAppCache(); + + private final Object mLock; + + private final Context mContext; + + private final int mUserId; + + private final RemotePrintSpooler mSpooler; + + private final Handler mHandler; + + private PrinterDiscoverySessionMediator mPrinterDiscoverySession; + + private List<PrintJobStateChangeListenerRecord> mPrintJobStateChangeListenerRecords; + + private boolean mDestroyed; + + public UserState(Context context, int userId, Object lock) { + mContext = context; + mUserId = userId; + mLock = lock; + mSpooler = new RemotePrintSpooler(context, userId, this); + mHandler = new UserStateHandler(context.getMainLooper()); + synchronized (mLock) { + enableSystemPrintServicesOnFirstBootLocked(); + } + } + + @Override + public void onPrintJobQueued(PrintJobInfo printJob) { + final RemotePrintService service; + synchronized (mLock) { + throwIfDestroyedLocked(); + ComponentName printServiceName = printJob.getPrinterId().getServiceName(); + service = mActiveServices.get(printServiceName); + } + if (service != null) { + service.onPrintJobQueued(printJob); + } else { + // The service for the job is no longer enabled, so just + // fail the job with the appropriate message. + mSpooler.setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED, + mContext.getString(R.string.reason_service_unavailable)); + } + } + + @Override + public void onAllPrintJobsForServiceHandled(ComponentName printService) { + final RemotePrintService service; + synchronized (mLock) { + throwIfDestroyedLocked(); + service = mActiveServices.get(printService); + } + if (service != null) { + service.onAllPrintJobsHandled(); + } + } + + public void removeObsoletePrintJobs() { + mSpooler.removeObsoletePrintJobs(); + } + + @SuppressWarnings("deprecation") + public Bundle print(String printJobName, IPrintDocumentAdapter adapter, + PrintAttributes attributes, String packageName, int appId) { + // Create print job place holder. + final PrintJobInfo printJob = new PrintJobInfo(); + printJob.setId(new PrintJobId()); + printJob.setAppId(appId); + printJob.setLabel(printJobName); + printJob.setAttributes(attributes); + printJob.setState(PrintJobInfo.STATE_CREATED); + printJob.setCopies(1); + printJob.setCreationTime(System.currentTimeMillis()); + + // Track this job so we can forget it when the creator dies. + if (!mPrintJobForAppCache.onPrintJobCreated(adapter.asBinder(), appId, + printJob)) { + // Not adding a print job means the client is dead - done. + return null; + } + + // Spin the spooler to add the job and show the config UI. + new AsyncTask<Void, Void, Void>() { + @Override + protected Void doInBackground(Void... params) { + mSpooler.createPrintJob(printJob); + return null; + } + }.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, (Void[]) null); + + final long identity = Binder.clearCallingIdentity(); + try { + Intent intent = new Intent(PrintManager.ACTION_PRINT_DIALOG); + intent.setData(Uri.fromParts("printjob", printJob.getId().flattenToString(), null)); + intent.putExtra(PrintManager.EXTRA_PRINT_DOCUMENT_ADAPTER, adapter.asBinder()); + intent.putExtra(PrintManager.EXTRA_PRINT_JOB, printJob); + intent.putExtra(DocumentsContract.EXTRA_PACKAGE_NAME, packageName); + + IntentSender intentSender = PendingIntent.getActivityAsUser( + mContext, 0, intent, PendingIntent.FLAG_ONE_SHOT + | PendingIntent.FLAG_CANCEL_CURRENT, null, new UserHandle(mUserId)) + .getIntentSender(); + + Bundle result = new Bundle(); + result.putParcelable(PrintManager.EXTRA_PRINT_JOB, printJob); + result.putParcelable(PrintManager.EXTRA_PRINT_DIALOG_INTENT, intentSender); + + return result; + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + public List<PrintJobInfo> getPrintJobInfos(int appId) { + List<PrintJobInfo> cachedPrintJobs = mPrintJobForAppCache.getPrintJobs(appId); + // Note that the print spooler is not storing print jobs that + // are in a terminal state as it is non-trivial to properly update + // the spooler state for when to forget print jobs in terminal state. + // Therefore, we fuse the cached print jobs for running apps (some + // jobs are in a terminal state) with the ones that the print + // spooler knows about (some jobs are being processed). + ArrayMap<PrintJobId, PrintJobInfo> result = + new ArrayMap<PrintJobId, PrintJobInfo>(); + + // Add the cached print jobs for running apps. + final int cachedPrintJobCount = cachedPrintJobs.size(); + for (int i = 0; i < cachedPrintJobCount; i++) { + PrintJobInfo cachedPrintJob = cachedPrintJobs.get(i); + result.put(cachedPrintJob.getId(), cachedPrintJob); + // Strip out the tag - it is visible only to print services. + // Also the cached print jobs are delivered only to apps, so + // stripping the tag of a cached print job is fine. + cachedPrintJob.setTag(null); + } + + // Add everything else the spooler knows about. + List<PrintJobInfo> printJobs = mSpooler.getPrintJobInfos(null, + PrintJobInfo.STATE_ANY, appId); + if (printJobs != null) { + final int printJobCount = printJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = printJobs.get(i); + result.put(printJob.getId(), printJob); + // Strip out the tag - it is visible only to print services. + printJob.setTag(null); + } + } + + return new ArrayList<PrintJobInfo>(result.values()); + } + + public PrintJobInfo getPrintJobInfo(PrintJobId printJobId, int appId) { + PrintJobInfo printJob = mPrintJobForAppCache.getPrintJob(printJobId, appId); + if (printJob != null) { + return printJob; + } + return mSpooler.getPrintJobInfo(printJobId, appId); + } + + public void cancelPrintJob(PrintJobId printJobId, int appId) { + PrintJobInfo printJobInfo = mSpooler.getPrintJobInfo(printJobId, appId); + if (printJobInfo == null) { + return; + } + + // Take a note that we are trying to cancel the job. + mSpooler.setPrintJobCancelling(printJobId, true); + + if (printJobInfo.getState() != PrintJobInfo.STATE_FAILED) { + ComponentName printServiceName = printJobInfo.getPrinterId().getServiceName(); + RemotePrintService printService = null; + synchronized (mLock) { + printService = mActiveServices.get(printServiceName); + } + if (printService == null) { + return; + } + printService.onRequestCancelPrintJob(printJobInfo); + } else { + // If the print job is failed we do not need cooperation + // from the print service. + mSpooler.setPrintJobState(printJobId, PrintJobInfo.STATE_CANCELED, null); + } + } + + public void restartPrintJob(PrintJobId printJobId, int appId) { + PrintJobInfo printJobInfo = getPrintJobInfo(printJobId, appId); + if (printJobInfo == null || printJobInfo.getState() != PrintJobInfo.STATE_FAILED) { + return; + } + mSpooler.setPrintJobState(printJobId, PrintJobInfo.STATE_QUEUED, null); + } + + public List<PrintServiceInfo> getEnabledPrintServices() { + synchronized (mLock) { + List<PrintServiceInfo> enabledServices = null; + final int installedServiceCount = mInstalledServices.size(); + for (int i = 0; i < installedServiceCount; i++) { + PrintServiceInfo installedService = mInstalledServices.get(i); + ComponentName componentName = new ComponentName( + installedService.getResolveInfo().serviceInfo.packageName, + installedService.getResolveInfo().serviceInfo.name); + if (mActiveServices.containsKey(componentName)) { + if (enabledServices == null) { + enabledServices = new ArrayList<PrintServiceInfo>(); + } + enabledServices.add(installedService); + } + } + return enabledServices; + } + } + + public List<PrintServiceInfo> getInstalledPrintServices() { + synchronized (mLock) { + return mInstalledServices; + } + } + + public void createPrinterDiscoverySession(IPrinterDiscoveryObserver observer) { + synchronized (mLock) { + throwIfDestroyedLocked(); + if (mActiveServices.isEmpty()) { + return; + } + if (mPrinterDiscoverySession == null) { + // If we do not have a session, tell all service to create one. + mPrinterDiscoverySession = new PrinterDiscoverySessionMediator(mContext) { + @Override + public void onDestroyed() { + mPrinterDiscoverySession = null; + } + }; + // Add the observer to the brand new session. + mPrinterDiscoverySession.addObserverLocked(observer); + } else { + // If services have created session, just add the observer. + mPrinterDiscoverySession.addObserverLocked(observer); + } + } + } + + public void destroyPrinterDiscoverySession(IPrinterDiscoveryObserver observer) { + synchronized (mLock) { + // Already destroyed - nothing to do. + if (mPrinterDiscoverySession == null) { + return; + } + // Remove this observer. + mPrinterDiscoverySession.removeObserverLocked(observer); + } + } + + public void startPrinterDiscovery(IPrinterDiscoveryObserver observer, + List<PrinterId> printerIds) { + synchronized (mLock) { + throwIfDestroyedLocked(); + // No services - nothing to do. + if (mActiveServices.isEmpty()) { + return; + } + // No session - nothing to do. + if (mPrinterDiscoverySession == null) { + return; + } + // Kick of discovery. + mPrinterDiscoverySession.startPrinterDiscoveryLocked(observer, + printerIds); + } + } + + public void stopPrinterDiscovery(IPrinterDiscoveryObserver observer) { + synchronized (mLock) { + throwIfDestroyedLocked(); + // No services - nothing to do. + if (mActiveServices.isEmpty()) { + return; + } + // No session - nothing to do. + if (mPrinterDiscoverySession == null) { + return; + } + // Kick of discovery. + mPrinterDiscoverySession.stopPrinterDiscoveryLocked(observer); + } + } + + public void validatePrinters(List<PrinterId> printerIds) { + synchronized (mLock) { + throwIfDestroyedLocked(); + // No services - nothing to do. + if (mActiveServices.isEmpty()) { + return; + } + // No session - nothing to do. + if (mPrinterDiscoverySession == null) { + return; + } + // Request an updated. + mPrinterDiscoverySession.validatePrintersLocked(printerIds); + } + } + + public void startPrinterStateTracking(PrinterId printerId) { + synchronized (mLock) { + throwIfDestroyedLocked(); + // No services - nothing to do. + if (mActiveServices.isEmpty()) { + return; + } + // No session - nothing to do. + if (mPrinterDiscoverySession == null) { + return; + } + // Request start tracking the printer. + mPrinterDiscoverySession.startPrinterStateTrackingLocked(printerId); + } + } + + public void stopPrinterStateTracking(PrinterId printerId) { + synchronized (mLock) { + throwIfDestroyedLocked(); + // No services - nothing to do. + if (mActiveServices.isEmpty()) { + return; + } + // No session - nothing to do. + if (mPrinterDiscoverySession == null) { + return; + } + // Request stop tracking the printer. + mPrinterDiscoverySession.stopPrinterStateTrackingLocked(printerId); + } + } + + public void addPrintJobStateChangeListener(IPrintJobStateChangeListener listener, + int appId) throws RemoteException { + synchronized (mLock) { + throwIfDestroyedLocked(); + if (mPrintJobStateChangeListenerRecords == null) { + mPrintJobStateChangeListenerRecords = + new ArrayList<PrintJobStateChangeListenerRecord>(); + } + mPrintJobStateChangeListenerRecords.add( + new PrintJobStateChangeListenerRecord(listener, appId) { + @Override + public void onBinderDied() { + mPrintJobStateChangeListenerRecords.remove(this); + } + }); + } + } + + public void removePrintJobStateChangeListener(IPrintJobStateChangeListener listener) { + synchronized (mLock) { + throwIfDestroyedLocked(); + if (mPrintJobStateChangeListenerRecords == null) { + return; + } + final int recordCount = mPrintJobStateChangeListenerRecords.size(); + for (int i = 0; i < recordCount; i++) { + PrintJobStateChangeListenerRecord record = + mPrintJobStateChangeListenerRecords.get(i); + if (record.listener.asBinder().equals(listener.asBinder())) { + mPrintJobStateChangeListenerRecords.remove(i); + break; + } + } + if (mPrintJobStateChangeListenerRecords.isEmpty()) { + mPrintJobStateChangeListenerRecords = null; + } + } + } + + @Override + public void onPrintJobStateChanged(PrintJobInfo printJob) { + mPrintJobForAppCache.onPrintJobStateChanged(printJob); + mHandler.obtainMessage(UserStateHandler.MSG_DISPATCH_PRINT_JOB_STATE_CHANGED, + printJob.getAppId(), 0, printJob.getId()).sendToTarget(); + } + + @Override + public void onPrintersAdded(List<PrinterInfo> printers) { + synchronized (mLock) { + throwIfDestroyedLocked(); + // No services - nothing to do. + if (mActiveServices.isEmpty()) { + return; + } + // No session - nothing to do. + if (mPrinterDiscoverySession == null) { + return; + } + mPrinterDiscoverySession.onPrintersAddedLocked(printers); + } + } + + @Override + public void onPrintersRemoved(List<PrinterId> printerIds) { + synchronized (mLock) { + throwIfDestroyedLocked(); + // No services - nothing to do. + if (mActiveServices.isEmpty()) { + return; + } + // No session - nothing to do. + if (mPrinterDiscoverySession == null) { + return; + } + mPrinterDiscoverySession.onPrintersRemovedLocked(printerIds); + } + } + + @Override + public void onServiceDied(RemotePrintService service) { + synchronized (mLock) { + throwIfDestroyedLocked(); + // No services - nothing to do. + if (mActiveServices.isEmpty()) { + return; + } + // Fail all print jobs. + failActivePrintJobsForService(service.getComponentName()); + service.onAllPrintJobsHandled(); + // No session - nothing to do. + if (mPrinterDiscoverySession == null) { + return; + } + mPrinterDiscoverySession.onServiceDiedLocked(service); + } + } + + public void updateIfNeededLocked() { + throwIfDestroyedLocked(); + if (readConfigurationLocked()) { + onConfigurationChangedLocked(); + } + } + + public Set<ComponentName> getEnabledServices() { + synchronized(mLock) { + throwIfDestroyedLocked(); + return mEnabledServices; + } + } + + public void destroyLocked() { + throwIfDestroyedLocked(); + mSpooler.destroy(); + for (RemotePrintService service : mActiveServices.values()) { + service.destroy(); + } + mActiveServices.clear(); + mInstalledServices.clear(); + mEnabledServices.clear(); + if (mPrinterDiscoverySession != null) { + mPrinterDiscoverySession.destroyLocked(); + mPrinterDiscoverySession = null; + } + mDestroyed = true; + } + + public void dump(FileDescriptor fd, PrintWriter pw, String prefix) { + pw.append(prefix).append("user state ").append(String.valueOf(mUserId)).append(":"); + pw.println(); + + String tab = " "; + + pw.append(prefix).append(tab).append("installed services:").println(); + final int installedServiceCount = mInstalledServices.size(); + for (int i = 0; i < installedServiceCount; i++) { + PrintServiceInfo installedService = mInstalledServices.get(i); + String installedServicePrefix = prefix + tab + tab; + pw.append(installedServicePrefix).append("service:").println(); + ResolveInfo resolveInfo = installedService.getResolveInfo(); + ComponentName componentName = new ComponentName( + resolveInfo.serviceInfo.packageName, + resolveInfo.serviceInfo.name); + pw.append(installedServicePrefix).append(tab).append("componentName=") + .append(componentName.flattenToString()).println(); + pw.append(installedServicePrefix).append(tab).append("settingsActivity=") + .append(installedService.getSettingsActivityName()).println(); + pw.append(installedServicePrefix).append(tab).append("addPrintersActivity=") + .append(installedService.getAddPrintersActivityName()).println(); + } + + pw.append(prefix).append(tab).append("enabled services:").println(); + for (ComponentName enabledService : mEnabledServices) { + String enabledServicePrefix = prefix + tab + tab; + pw.append(enabledServicePrefix).append("service:").println(); + pw.append(enabledServicePrefix).append(tab).append("componentName=") + .append(enabledService.flattenToString()); + pw.println(); + } + + pw.append(prefix).append(tab).append("active services:").println(); + final int activeServiceCount = mActiveServices.size(); + for (int i = 0; i < activeServiceCount; i++) { + RemotePrintService activeService = mActiveServices.valueAt(i); + activeService.dump(pw, prefix + tab + tab); + pw.println(); + } + + pw.append(prefix).append(tab).append("cached print jobs:").println(); + mPrintJobForAppCache.dump(pw, prefix + tab + tab); + + pw.append(prefix).append(tab).append("discovery mediator:").println(); + if (mPrinterDiscoverySession != null) { + mPrinterDiscoverySession.dump(pw, prefix + tab + tab); + } + + pw.append(prefix).append(tab).append("print spooler:").println(); + mSpooler.dump(fd, pw, prefix + tab + tab); + pw.println(); + } + + private boolean readConfigurationLocked() { + boolean somethingChanged = false; + somethingChanged |= readInstalledPrintServicesLocked(); + somethingChanged |= readEnabledPrintServicesLocked(); + return somethingChanged; + } + + private boolean readInstalledPrintServicesLocked() { + Set<PrintServiceInfo> tempPrintServices = new HashSet<PrintServiceInfo>(); + + List<ResolveInfo> installedServices = mContext.getPackageManager() + .queryIntentServicesAsUser(mQueryIntent, PackageManager.GET_SERVICES + | PackageManager.GET_META_DATA, mUserId); + + final int installedCount = installedServices.size(); + for (int i = 0, count = installedCount; i < count; i++) { + ResolveInfo installedService = installedServices.get(i); + if (!android.Manifest.permission.BIND_PRINT_SERVICE.equals( + installedService.serviceInfo.permission)) { + ComponentName serviceName = new ComponentName( + installedService.serviceInfo.packageName, + installedService.serviceInfo.name); + Slog.w(LOG_TAG, "Skipping print service " + + serviceName.flattenToShortString() + + " since it does not require permission " + + android.Manifest.permission.BIND_PRINT_SERVICE); + continue; + } + tempPrintServices.add(PrintServiceInfo.create(installedService, mContext)); + } + + if (!tempPrintServices.equals(mInstalledServices)) { + mInstalledServices.clear(); + mInstalledServices.addAll(tempPrintServices); + return true; + } + + return false; + } + + private boolean readEnabledPrintServicesLocked() { + Set<ComponentName> tempEnabledServiceNameSet = new HashSet<ComponentName>(); + readPrintServicesFromSettingLocked(Settings.Secure.ENABLED_PRINT_SERVICES, + tempEnabledServiceNameSet); + if (!tempEnabledServiceNameSet.equals(mEnabledServices)) { + mEnabledServices.clear(); + mEnabledServices.addAll(tempEnabledServiceNameSet); + return true; + } + return false; + } + + private void readPrintServicesFromSettingLocked(String setting, + Set<ComponentName> outServiceNames) { + String settingValue = Settings.Secure.getStringForUser(mContext.getContentResolver(), + setting, mUserId); + if (!TextUtils.isEmpty(settingValue)) { + TextUtils.SimpleStringSplitter splitter = mStringColonSplitter; + splitter.setString(settingValue); + while (splitter.hasNext()) { + String string = splitter.next(); + if (TextUtils.isEmpty(string)) { + continue; + } + ComponentName componentName = ComponentName.unflattenFromString(string); + if (componentName != null) { + outServiceNames.add(componentName); + } + } + } + } + + private void enableSystemPrintServicesOnFirstBootLocked() { + // Load enabled and installed services. + readEnabledPrintServicesLocked(); + readInstalledPrintServicesLocked(); + + // Load the system services once enabled on first boot. + Set<ComponentName> enabledOnFirstBoot = new HashSet<ComponentName>(); + readPrintServicesFromSettingLocked( + Settings.Secure.ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES, + enabledOnFirstBoot); + + StringBuilder builder = new StringBuilder(); + + final int serviceCount = mInstalledServices.size(); + for (int i = 0; i < serviceCount; i++) { + ServiceInfo serviceInfo = mInstalledServices.get(i).getResolveInfo().serviceInfo; + // Enable system print services if we never did that and are not enabled. + if ((serviceInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0) { + ComponentName serviceName = new ComponentName( + serviceInfo.packageName, serviceInfo.name); + if (!mEnabledServices.contains(serviceName) + && !enabledOnFirstBoot.contains(serviceName)) { + if (builder.length() > 0) { + builder.append(":"); + } + builder.append(serviceName.flattenToString()); + } + } + } + + // Nothing to be enabled - done. + if (builder.length() <= 0) { + return; + } + + String servicesToEnable = builder.toString(); + + // Update the enabled services setting. + String enabledServices = Settings.Secure.getStringForUser( + mContext.getContentResolver(), Settings.Secure.ENABLED_PRINT_SERVICES, mUserId); + if (TextUtils.isEmpty(enabledServices)) { + enabledServices = servicesToEnable; + } else { + enabledServices = enabledServices + ":" + servicesToEnable; + } + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.ENABLED_PRINT_SERVICES, enabledServices, mUserId); + + // Update the enabled on first boot services setting. + String enabledOnFirstBootServices = Settings.Secure.getStringForUser( + mContext.getContentResolver(), + Settings.Secure.ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES, mUserId); + if (TextUtils.isEmpty(enabledOnFirstBootServices)) { + enabledOnFirstBootServices = servicesToEnable; + } else { + enabledOnFirstBootServices = enabledOnFirstBootServices + ":" + enabledServices; + } + Settings.Secure.putStringForUser(mContext.getContentResolver(), + Settings.Secure.ENABLED_ON_FIRST_BOOT_SYSTEM_PRINT_SERVICES, + enabledOnFirstBootServices, mUserId); + } + + private void onConfigurationChangedLocked() { + final int installedCount = mInstalledServices.size(); + for (int i = 0; i < installedCount; i++) { + ResolveInfo resolveInfo = mInstalledServices.get(i).getResolveInfo(); + ComponentName serviceName = new ComponentName(resolveInfo.serviceInfo.packageName, + resolveInfo.serviceInfo.name); + if (mEnabledServices.contains(serviceName)) { + if (!mActiveServices.containsKey(serviceName)) { + RemotePrintService service = new RemotePrintService( + mContext, serviceName, mUserId, mSpooler, this); + addServiceLocked(service); + } + } else { + RemotePrintService service = mActiveServices.remove(serviceName); + if (service != null) { + removeServiceLocked(service); + } + } + } + } + + private void addServiceLocked(RemotePrintService service) { + mActiveServices.put(service.getComponentName(), service); + if (mPrinterDiscoverySession != null) { + mPrinterDiscoverySession.onServiceAddedLocked(service); + } + } + + private void removeServiceLocked(RemotePrintService service) { + // Fail all print jobs. + failActivePrintJobsForService(service.getComponentName()); + // If discovery is in progress, tear down the service. + if (mPrinterDiscoverySession != null) { + mPrinterDiscoverySession.onServiceRemovedLocked(service); + } else { + // Otherwise, just destroy it. + service.destroy(); + } + } + + private void failActivePrintJobsForService(final ComponentName serviceName) { + // Makes sure all active print jobs are failed since the service + // just died. Do this off the main thread since we do to allow + // calls into the spooler on the main thread. + if (Looper.getMainLooper().isCurrentThread()) { + BackgroundThread.getHandler().post(new Runnable() { + @Override + public void run() { + failScheduledPrintJobsForServiceInternal(serviceName); + } + }); + } else { + failScheduledPrintJobsForServiceInternal(serviceName); + } + } + + private void failScheduledPrintJobsForServiceInternal(ComponentName serviceName) { + List<PrintJobInfo> printJobs = mSpooler.getPrintJobInfos(serviceName, + PrintJobInfo.STATE_ANY_SCHEDULED, PrintManager.APP_ID_ANY); + if (printJobs == null) { + return; + } + final long identity = Binder.clearCallingIdentity(); + try { + final int printJobCount = printJobs.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = printJobs.get(i); + mSpooler.setPrintJobState(printJob.getId(), PrintJobInfo.STATE_FAILED, + mContext.getString(R.string.reason_service_unavailable)); + } + } finally { + Binder.restoreCallingIdentity(identity); + } + } + + private void throwIfDestroyedLocked() { + if (mDestroyed) { + throw new IllegalStateException("Cannot interact with a destroyed instance."); + } + } + + private void handleDispatchPrintJobStateChanged(PrintJobId printJobId, int appId) { + final List<PrintJobStateChangeListenerRecord> records; + synchronized (mLock) { + if (mPrintJobStateChangeListenerRecords == null) { + return; + } + records = new ArrayList<PrintJobStateChangeListenerRecord>( + mPrintJobStateChangeListenerRecords); + } + final int recordCount = records.size(); + for (int i = 0; i < recordCount; i++) { + PrintJobStateChangeListenerRecord record = records.get(i); + if (record.appId == PrintManager.APP_ID_ANY + || record.appId == appId) + try { + record.listener.onPrintJobStateChanged(printJobId); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error notifying for print job state change", re); + } + } + } + + private final class UserStateHandler extends Handler { + public static final int MSG_DISPATCH_PRINT_JOB_STATE_CHANGED = 1; + + public UserStateHandler(Looper looper) { + super(looper, null, false); + } + + @Override + public void handleMessage(Message message) { + if (message.what == MSG_DISPATCH_PRINT_JOB_STATE_CHANGED) { + PrintJobId printJobId = (PrintJobId) message.obj; + final int appId = message.arg1; + handleDispatchPrintJobStateChanged(printJobId, appId); + } + } + } + + private abstract class PrintJobStateChangeListenerRecord implements DeathRecipient { + final IPrintJobStateChangeListener listener; + final int appId; + + public PrintJobStateChangeListenerRecord(IPrintJobStateChangeListener listener, + int appId) throws RemoteException { + this.listener = listener; + this.appId = appId; + listener.asBinder().linkToDeath(this, 0); + } + + @Override + public void binderDied() { + listener.asBinder().unlinkToDeath(this, 0); + onBinderDied(); + } + + public abstract void onBinderDied(); + } + + private class PrinterDiscoverySessionMediator { + private final ArrayMap<PrinterId, PrinterInfo> mPrinters = + new ArrayMap<PrinterId, PrinterInfo>(); + + private final RemoteCallbackList<IPrinterDiscoveryObserver> mDiscoveryObservers = + new RemoteCallbackList<IPrinterDiscoveryObserver>() { + @Override + public void onCallbackDied(IPrinterDiscoveryObserver observer) { + synchronized (mLock) { + stopPrinterDiscoveryLocked(observer); + removeObserverLocked(observer); + } + } + }; + + private final List<IBinder> mStartedPrinterDiscoveryTokens = new ArrayList<IBinder>(); + + private final List<PrinterId> mStateTrackedPrinters = new ArrayList<PrinterId>(); + + private final Handler mHandler; + + private boolean mIsDestroyed; + + public PrinterDiscoverySessionMediator(Context context) { + mHandler = new SessionHandler(context.getMainLooper()); + // Kick off the session creation. + List<RemotePrintService> services = new ArrayList<RemotePrintService>( + mActiveServices.values()); + mHandler.obtainMessage(SessionHandler + .MSG_DISPATCH_CREATE_PRINTER_DISCOVERY_SESSION, services) + .sendToTarget(); + } + + public void addObserverLocked(IPrinterDiscoveryObserver observer) { + // Add the observer. + mDiscoveryObservers.register(observer); + + // Bring the added observer up to speed with the printers. + if (!mPrinters.isEmpty()) { + List<PrinterInfo> printers = new ArrayList<PrinterInfo>(mPrinters.values()); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = observer; + args.arg2 = printers; + mHandler.obtainMessage(SessionHandler.MSG_PRINTERS_ADDED, + args).sendToTarget(); + } + } + + public void removeObserverLocked(IPrinterDiscoveryObserver observer) { + // Remove the observer. + mDiscoveryObservers.unregister(observer); + // No one else observing - then kill it. + if (mDiscoveryObservers.getRegisteredCallbackCount() == 0) { + destroyLocked(); + } + } + + public final void startPrinterDiscoveryLocked(IPrinterDiscoveryObserver observer, + List<PrinterId> priorityList) { + if (mIsDestroyed) { + Log.w(LOG_TAG, "Not starting dicovery - session destroyed"); + return; + } + + final boolean discoveryStarted = !mStartedPrinterDiscoveryTokens.isEmpty(); + + // Remember we got a start request to match with an end. + mStartedPrinterDiscoveryTokens.add(observer.asBinder()); + + // If printer discovery is ongoing and the start request has a list + // of printer to be checked, then we just request validating them. + if (discoveryStarted && priorityList != null && !priorityList.isEmpty()) { + validatePrinters(priorityList); + return; + } + + // The service are already performing discovery - nothing to do. + if (mStartedPrinterDiscoveryTokens.size() > 1) { + return; + } + + List<RemotePrintService> services = new ArrayList<RemotePrintService>( + mActiveServices.values()); + SomeArgs args = SomeArgs.obtain(); + args.arg1 = services; + args.arg2 = priorityList; + mHandler.obtainMessage(SessionHandler + .MSG_DISPATCH_START_PRINTER_DISCOVERY, args) + .sendToTarget(); + } + + public final void stopPrinterDiscoveryLocked(IPrinterDiscoveryObserver observer) { + if (mIsDestroyed) { + Log.w(LOG_TAG, "Not stopping dicovery - session destroyed"); + return; + } + // This one did not make an active discovery request - nothing to do. + if (!mStartedPrinterDiscoveryTokens.remove(observer.asBinder())) { + return; + } + // There are other interested observers - do not stop discovery. + if (!mStartedPrinterDiscoveryTokens.isEmpty()) { + return; + } + List<RemotePrintService> services = new ArrayList<RemotePrintService>( + mActiveServices.values()); + mHandler.obtainMessage(SessionHandler + .MSG_DISPATCH_STOP_PRINTER_DISCOVERY, services) + .sendToTarget(); + } + + public void validatePrintersLocked(List<PrinterId> printerIds) { + if (mIsDestroyed) { + Log.w(LOG_TAG, "Not validating pritners - session destroyed"); + return; + } + + List<PrinterId> remainingList = new ArrayList<PrinterId>(printerIds); + while (!remainingList.isEmpty()) { + Iterator<PrinterId> iterator = remainingList.iterator(); + // Gather the printers per service and request a validation. + List<PrinterId> updateList = new ArrayList<PrinterId>(); + ComponentName serviceName = null; + while (iterator.hasNext()) { + PrinterId printerId = iterator.next(); + if (updateList.isEmpty()) { + updateList.add(printerId); + serviceName = printerId.getServiceName(); + iterator.remove(); + } else if (printerId.getServiceName().equals(serviceName)) { + updateList.add(printerId); + iterator.remove(); + } + } + // Schedule a notification of the service. + RemotePrintService service = mActiveServices.get(serviceName); + if (service != null) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = service; + args.arg2 = updateList; + mHandler.obtainMessage(SessionHandler + .MSG_VALIDATE_PRINTERS, args) + .sendToTarget(); + } + } + } + + public final void startPrinterStateTrackingLocked(PrinterId printerId) { + if (mIsDestroyed) { + Log.w(LOG_TAG, "Not starting printer state tracking - session destroyed"); + return; + } + // If printer discovery is not started - nothing to do. + if (mStartedPrinterDiscoveryTokens.isEmpty()) { + return; + } + final boolean containedPrinterId = mStateTrackedPrinters.contains(printerId); + // Keep track of the number of requests to track this one. + mStateTrackedPrinters.add(printerId); + // If we were tracking this printer - nothing to do. + if (containedPrinterId) { + return; + } + // No service - nothing to do. + RemotePrintService service = mActiveServices.get(printerId.getServiceName()); + if (service == null) { + return; + } + // Ask the service to start tracking. + SomeArgs args = SomeArgs.obtain(); + args.arg1 = service; + args.arg2 = printerId; + mHandler.obtainMessage(SessionHandler + .MSG_START_PRINTER_STATE_TRACKING, args) + .sendToTarget(); + } + + public final void stopPrinterStateTrackingLocked(PrinterId printerId) { + if (mIsDestroyed) { + Log.w(LOG_TAG, "Not stopping printer state tracking - session destroyed"); + return; + } + // If printer discovery is not started - nothing to do. + if (mStartedPrinterDiscoveryTokens.isEmpty()) { + return; + } + // If we did not track this printer - nothing to do. + if (!mStateTrackedPrinters.remove(printerId)) { + return; + } + // No service - nothing to do. + RemotePrintService service = mActiveServices.get(printerId.getServiceName()); + if (service == null) { + return; + } + // Ask the service to start tracking. + SomeArgs args = SomeArgs.obtain(); + args.arg1 = service; + args.arg2 = printerId; + mHandler.obtainMessage(SessionHandler + .MSG_STOP_PRINTER_STATE_TRACKING, args) + .sendToTarget(); + } + + public void onDestroyed() { + /* do nothing */ + } + + public void destroyLocked() { + if (mIsDestroyed) { + Log.w(LOG_TAG, "Not destroying - session destroyed"); + return; + } + // Make sure printer tracking is stopped. + final int printerCount = mStateTrackedPrinters.size(); + for (int i = 0; i < printerCount; i++) { + PrinterId printerId = mStateTrackedPrinters.get(i); + stopPrinterStateTracking(printerId); + } + // Make sure discovery is stopped. + final int observerCount = mStartedPrinterDiscoveryTokens.size(); + for (int i = 0; i < observerCount; i++) { + IBinder token = mStartedPrinterDiscoveryTokens.get(i); + stopPrinterDiscoveryLocked(IPrinterDiscoveryObserver.Stub.asInterface(token)); + } + // Tell the services we are done. + List<RemotePrintService> services = new ArrayList<RemotePrintService>( + mActiveServices.values()); + mHandler.obtainMessage(SessionHandler + .MSG_DISPATCH_DESTROY_PRINTER_DISCOVERY_SESSION, services) + .sendToTarget(); + } + + public void onPrintersAddedLocked(List<PrinterInfo> printers) { + if (DEBUG) { + Log.i(LOG_TAG, "onPrintersAddedLocked()"); + } + if (mIsDestroyed) { + Log.w(LOG_TAG, "Not adding printers - session destroyed"); + return; + } + List<PrinterInfo> addedPrinters = null; + final int addedPrinterCount = printers.size(); + for (int i = 0; i < addedPrinterCount; i++) { + PrinterInfo printer = printers.get(i); + PrinterInfo oldPrinter = mPrinters.put(printer.getId(), printer); + if (oldPrinter == null || !oldPrinter.equals(printer)) { + if (addedPrinters == null) { + addedPrinters = new ArrayList<PrinterInfo>(); + } + addedPrinters.add(printer); + } + } + if (addedPrinters != null) { + mHandler.obtainMessage(SessionHandler.MSG_DISPATCH_PRINTERS_ADDED, + addedPrinters).sendToTarget(); + } + } + + public void onPrintersRemovedLocked(List<PrinterId> printerIds) { + if (DEBUG) { + Log.i(LOG_TAG, "onPrintersRemovedLocked()"); + } + if (mIsDestroyed) { + Log.w(LOG_TAG, "Not removing printers - session destroyed"); + return; + } + List<PrinterId> removedPrinterIds = null; + final int removedPrinterCount = printerIds.size(); + for (int i = 0; i < removedPrinterCount; i++) { + PrinterId removedPrinterId = printerIds.get(i); + if (mPrinters.remove(removedPrinterId) != null) { + if (removedPrinterIds == null) { + removedPrinterIds = new ArrayList<PrinterId>(); + } + removedPrinterIds.add(removedPrinterId); + } + } + if (removedPrinterIds != null) { + mHandler.obtainMessage(SessionHandler.MSG_DISPATCH_PRINTERS_REMOVED, + removedPrinterIds).sendToTarget(); + } + } + + public void onServiceRemovedLocked(RemotePrintService service) { + if (mIsDestroyed) { + Log.w(LOG_TAG, "Not updating removed service - session destroyed"); + return; + } + // Remove the reported and tracked printers for that service. + ComponentName serviceName = service.getComponentName(); + removePrintersForServiceLocked(serviceName); + service.destroy(); + } + + public void onServiceDiedLocked(RemotePrintService service) { + // Remove the reported by that service. + removePrintersForServiceLocked(service.getComponentName()); + } + + public void onServiceAddedLocked(RemotePrintService service) { + if (mIsDestroyed) { + Log.w(LOG_TAG, "Not updating added service - session destroyed"); + return; + } + // Tell the service to create a session. + mHandler.obtainMessage( + SessionHandler.MSG_CREATE_PRINTER_DISCOVERY_SESSION, + service).sendToTarget(); + // Start printer discovery if necessary. + if (!mStartedPrinterDiscoveryTokens.isEmpty()) { + mHandler.obtainMessage( + SessionHandler.MSG_START_PRINTER_DISCOVERY, + service).sendToTarget(); + } + // Start tracking printers if necessary + final int trackedPrinterCount = mStateTrackedPrinters.size(); + for (int i = 0; i < trackedPrinterCount; i++) { + PrinterId printerId = mStateTrackedPrinters.get(i); + if (printerId.getServiceName().equals(service.getComponentName())) { + SomeArgs args = SomeArgs.obtain(); + args.arg1 = service; + args.arg2 = printerId; + mHandler.obtainMessage(SessionHandler + .MSG_START_PRINTER_STATE_TRACKING, args) + .sendToTarget(); + } + } + } + + public void dump(PrintWriter pw, String prefix) { + pw.append(prefix).append("destroyed=") + .append(String.valueOf(mDestroyed)).println(); + + pw.append(prefix).append("printDiscoveryInProgress=") + .append(String.valueOf(!mStartedPrinterDiscoveryTokens.isEmpty())).println(); + + String tab = " "; + + pw.append(prefix).append(tab).append("printer discovery observers:").println(); + final int observerCount = mDiscoveryObservers.beginBroadcast(); + for (int i = 0; i < observerCount; i++) { + IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i); + pw.append(prefix).append(prefix).append(observer.toString()); + pw.println(); + } + mDiscoveryObservers.finishBroadcast(); + + pw.append(prefix).append(tab).append("start discovery requests:").println(); + final int tokenCount = this.mStartedPrinterDiscoveryTokens.size(); + for (int i = 0; i < tokenCount; i++) { + IBinder token = mStartedPrinterDiscoveryTokens.get(i); + pw.append(prefix).append(tab).append(tab).append(token.toString()).println(); + } + + pw.append(prefix).append(tab).append("tracked printer requests:").println(); + final int trackedPrinters = mStateTrackedPrinters.size(); + for (int i = 0; i < trackedPrinters; i++) { + PrinterId printer = mStateTrackedPrinters.get(i); + pw.append(prefix).append(tab).append(tab).append(printer.toString()).println(); + } + + pw.append(prefix).append(tab).append("printers:").println(); + final int pritnerCount = mPrinters.size(); + for (int i = 0; i < pritnerCount; i++) { + PrinterInfo printer = mPrinters.valueAt(i); + pw.append(prefix).append(tab).append(tab).append( + printer.toString()).println(); + } + } + + private void removePrintersForServiceLocked(ComponentName serviceName) { + // No printers - nothing to do. + if (mPrinters.isEmpty()) { + return; + } + // Remove the printers for that service. + List<PrinterId> removedPrinterIds = null; + final int printerCount = mPrinters.size(); + for (int i = 0; i < printerCount; i++) { + PrinterId printerId = mPrinters.keyAt(i); + if (printerId.getServiceName().equals(serviceName)) { + if (removedPrinterIds == null) { + removedPrinterIds = new ArrayList<PrinterId>(); + } + removedPrinterIds.add(printerId); + } + } + if (removedPrinterIds != null) { + final int removedPrinterCount = removedPrinterIds.size(); + for (int i = 0; i < removedPrinterCount; i++) { + mPrinters.remove(removedPrinterIds.get(i)); + } + mHandler.obtainMessage( + SessionHandler.MSG_DISPATCH_PRINTERS_REMOVED, + removedPrinterIds).sendToTarget(); + } + } + + private void handleDispatchPrintersAdded(List<PrinterInfo> addedPrinters) { + final int observerCount = mDiscoveryObservers.beginBroadcast(); + for (int i = 0; i < observerCount; i++) { + IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i); + handlePrintersAdded(observer, addedPrinters); + } + mDiscoveryObservers.finishBroadcast(); + } + + private void handleDispatchPrintersRemoved(List<PrinterId> removedPrinterIds) { + final int observerCount = mDiscoveryObservers.beginBroadcast(); + for (int i = 0; i < observerCount; i++) { + IPrinterDiscoveryObserver observer = mDiscoveryObservers.getBroadcastItem(i); + handlePrintersRemoved(observer, removedPrinterIds); + } + mDiscoveryObservers.finishBroadcast(); + } + + private void handleDispatchCreatePrinterDiscoverySession( + List<RemotePrintService> services) { + final int serviceCount = services.size(); + for (int i = 0; i < serviceCount; i++) { + RemotePrintService service = services.get(i); + service.createPrinterDiscoverySession(); + } + } + + private void handleDispatchDestroyPrinterDiscoverySession( + List<RemotePrintService> services) { + final int serviceCount = services.size(); + for (int i = 0; i < serviceCount; i++) { + RemotePrintService service = services.get(i); + service.destroyPrinterDiscoverySession(); + } + onDestroyed(); + } + + private void handleDispatchStartPrinterDiscovery( + List<RemotePrintService> services, List<PrinterId> printerIds) { + final int serviceCount = services.size(); + for (int i = 0; i < serviceCount; i++) { + RemotePrintService service = services.get(i); + service.startPrinterDiscovery(printerIds); + } + } + + private void handleDispatchStopPrinterDiscovery(List<RemotePrintService> services) { + final int serviceCount = services.size(); + for (int i = 0; i < serviceCount; i++) { + RemotePrintService service = services.get(i); + service.stopPrinterDiscovery(); + } + } + + private void handleValidatePrinters(RemotePrintService service, + List<PrinterId> printerIds) { + service.validatePrinters(printerIds); + } + + private void handleStartPrinterStateTracking(RemotePrintService service, + PrinterId printerId) { + service.startPrinterStateTracking(printerId); + } + + private void handleStopPrinterStateTracking(RemotePrintService service, + PrinterId printerId) { + service.stopPrinterStateTracking(printerId); + } + + private void handlePrintersAdded(IPrinterDiscoveryObserver observer, + List<PrinterInfo> printers) { + try { + observer.onPrintersAdded(new ParceledListSlice<PrinterInfo>(printers)); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error sending added printers", re); + } + } + + private void handlePrintersRemoved(IPrinterDiscoveryObserver observer, + List<PrinterId> printerIds) { + try { + observer.onPrintersRemoved(new ParceledListSlice<PrinterId>(printerIds)); + } catch (RemoteException re) { + Log.e(LOG_TAG, "Error sending removed printers", re); + } + } + + private final class SessionHandler extends Handler { + public static final int MSG_PRINTERS_ADDED = 1; + public static final int MSG_PRINTERS_REMOVED = 2; + public static final int MSG_DISPATCH_PRINTERS_ADDED = 3; + public static final int MSG_DISPATCH_PRINTERS_REMOVED = 4; + + public static final int MSG_CREATE_PRINTER_DISCOVERY_SESSION = 5; + public static final int MSG_DESTROY_PRINTER_DISCOVERY_SESSION = 6; + public static final int MSG_START_PRINTER_DISCOVERY = 7; + public static final int MSG_STOP_PRINTER_DISCOVERY = 8; + public static final int MSG_DISPATCH_CREATE_PRINTER_DISCOVERY_SESSION = 9; + public static final int MSG_DISPATCH_DESTROY_PRINTER_DISCOVERY_SESSION = 10; + public static final int MSG_DISPATCH_START_PRINTER_DISCOVERY = 11; + public static final int MSG_DISPATCH_STOP_PRINTER_DISCOVERY = 12; + public static final int MSG_VALIDATE_PRINTERS = 13; + public static final int MSG_START_PRINTER_STATE_TRACKING = 14; + public static final int MSG_STOP_PRINTER_STATE_TRACKING = 15; + public static final int MSG_DESTROY_SERVICE = 16; + + SessionHandler(Looper looper) { + super(looper, null, false); + } + + @Override + @SuppressWarnings("unchecked") + public void handleMessage(Message message) { + switch (message.what) { + case MSG_PRINTERS_ADDED: { + SomeArgs args = (SomeArgs) message.obj; + IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) args.arg1; + List<PrinterInfo> addedPrinters = (List<PrinterInfo>) args.arg2; + args.recycle(); + handlePrintersAdded(observer, addedPrinters); + } break; + + case MSG_PRINTERS_REMOVED: { + SomeArgs args = (SomeArgs) message.obj; + IPrinterDiscoveryObserver observer = (IPrinterDiscoveryObserver) args.arg1; + List<PrinterId> removedPrinterIds = (List<PrinterId>) args.arg2; + args.recycle(); + handlePrintersRemoved(observer, removedPrinterIds); + } + + case MSG_DISPATCH_PRINTERS_ADDED: { + List<PrinterInfo> addedPrinters = (List<PrinterInfo>) message.obj; + handleDispatchPrintersAdded(addedPrinters); + } break; + + case MSG_DISPATCH_PRINTERS_REMOVED: { + List<PrinterId> removedPrinterIds = (List<PrinterId>) message.obj; + handleDispatchPrintersRemoved(removedPrinterIds); + } break; + + case MSG_CREATE_PRINTER_DISCOVERY_SESSION: { + RemotePrintService service = (RemotePrintService) message.obj; + service.createPrinterDiscoverySession(); + } break; + + case MSG_DESTROY_PRINTER_DISCOVERY_SESSION: { + RemotePrintService service = (RemotePrintService) message.obj; + service.destroyPrinterDiscoverySession(); + } break; + + case MSG_START_PRINTER_DISCOVERY: { + RemotePrintService service = (RemotePrintService) message.obj; + service.startPrinterDiscovery(null); + } break; + + case MSG_STOP_PRINTER_DISCOVERY: { + RemotePrintService service = (RemotePrintService) message.obj; + service.stopPrinterDiscovery(); + } break; + + case MSG_DISPATCH_CREATE_PRINTER_DISCOVERY_SESSION: { + List<RemotePrintService> services = (List<RemotePrintService>) message.obj; + handleDispatchCreatePrinterDiscoverySession(services); + } break; + + case MSG_DISPATCH_DESTROY_PRINTER_DISCOVERY_SESSION: { + List<RemotePrintService> services = (List<RemotePrintService>) message.obj; + handleDispatchDestroyPrinterDiscoverySession(services); + } break; + + case MSG_DISPATCH_START_PRINTER_DISCOVERY: { + SomeArgs args = (SomeArgs) message.obj; + List<RemotePrintService> services = (List<RemotePrintService>) args.arg1; + List<PrinterId> printerIds = (List<PrinterId>) args.arg2; + args.recycle(); + handleDispatchStartPrinterDiscovery(services, printerIds); + } break; + + case MSG_DISPATCH_STOP_PRINTER_DISCOVERY: { + List<RemotePrintService> services = (List<RemotePrintService>) message.obj; + handleDispatchStopPrinterDiscovery(services); + } break; + + case MSG_VALIDATE_PRINTERS: { + SomeArgs args = (SomeArgs) message.obj; + RemotePrintService service = (RemotePrintService) args.arg1; + List<PrinterId> printerIds = (List<PrinterId>) args.arg2; + args.recycle(); + handleValidatePrinters(service, printerIds); + } break; + + case MSG_START_PRINTER_STATE_TRACKING: { + SomeArgs args = (SomeArgs) message.obj; + RemotePrintService service = (RemotePrintService) args.arg1; + PrinterId printerId = (PrinterId) args.arg2; + args.recycle(); + handleStartPrinterStateTracking(service, printerId); + } break; + + case MSG_STOP_PRINTER_STATE_TRACKING: { + SomeArgs args = (SomeArgs) message.obj; + RemotePrintService service = (RemotePrintService) args.arg1; + PrinterId printerId = (PrinterId) args.arg2; + args.recycle(); + handleStopPrinterStateTracking(service, printerId); + } break; + + case MSG_DESTROY_SERVICE: { + RemotePrintService service = (RemotePrintService) message.obj; + service.destroy(); + } break; + } + } + } + } + + private final class PrintJobForAppCache { + private final SparseArray<List<PrintJobInfo>> mPrintJobsForRunningApp = + new SparseArray<List<PrintJobInfo>>(); + + public boolean onPrintJobCreated(final IBinder creator, final int appId, + PrintJobInfo printJob) { + try { + creator.linkToDeath(new DeathRecipient() { + @Override + public void binderDied() { + creator.unlinkToDeath(this, 0); + synchronized (mLock) { + mPrintJobsForRunningApp.remove(appId); + } + } + }, 0); + } catch (RemoteException re) { + /* The process is already dead - we just failed. */ + return false; + } + synchronized (mLock) { + List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId); + if (printJobsForApp == null) { + printJobsForApp = new ArrayList<PrintJobInfo>(); + mPrintJobsForRunningApp.put(appId, printJobsForApp); + } + printJobsForApp.add(printJob); + } + return true; + } + + public void onPrintJobStateChanged(PrintJobInfo printJob) { + synchronized (mLock) { + List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get( + printJob.getAppId()); + if (printJobsForApp == null) { + return; + } + final int printJobCount = printJobsForApp.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo oldPrintJob = printJobsForApp.get(i); + if (oldPrintJob.getId().equals(printJob.getId())) { + printJobsForApp.set(i, printJob); + } + } + } + } + + public PrintJobInfo getPrintJob(PrintJobId printJobId, int appId) { + synchronized (mLock) { + List<PrintJobInfo> printJobsForApp = mPrintJobsForRunningApp.get(appId); + if (printJobsForApp == null) { + return null; + } + final int printJobCount = printJobsForApp.size(); + for (int i = 0; i < printJobCount; i++) { + PrintJobInfo printJob = printJobsForApp.get(i); + if (printJob.getId().equals(printJobId)) { + return printJob; + } + } + } + return null; + } + + public List<PrintJobInfo> getPrintJobs(int appId) { + synchronized (mLock) { + List<PrintJobInfo> printJobs = null; + if (appId == PrintManager.APP_ID_ANY) { + final int bucketCount = mPrintJobsForRunningApp.size(); + for (int i = 0; i < bucketCount; i++) { + List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i); + if (printJobs == null) { + printJobs = new ArrayList<PrintJobInfo>(); + } + printJobs.addAll(bucket); + } + } else { + List<PrintJobInfo> bucket = mPrintJobsForRunningApp.get(appId); + if (bucket != null) { + if (printJobs == null) { + printJobs = new ArrayList<PrintJobInfo>(); + } + printJobs.addAll(bucket); + } + } + if (printJobs != null) { + return printJobs; + } + return Collections.emptyList(); + } + } + + public void dump(PrintWriter pw, String prefix) { + synchronized (mLock) { + String tab = " "; + final int bucketCount = mPrintJobsForRunningApp.size(); + for (int i = 0; i < bucketCount; i++) { + final int appId = mPrintJobsForRunningApp.keyAt(i); + pw.append(prefix).append("appId=" + appId).append(':').println(); + List<PrintJobInfo> bucket = mPrintJobsForRunningApp.valueAt(i); + final int printJobCount = bucket.size(); + for (int j = 0; j < printJobCount; j++) { + PrintJobInfo printJob = bucket.get(j); + pw.append(prefix).append(tab).append(printJob.toString()).println(); + } + } + } + } + } +} diff --git a/services/java/com/android/server/updates/IntentFirewallInstallReceiver.java b/services/java/com/android/server/updates/IntentFirewallInstallReceiver.java index 91859033272a..0b54f92103dd 100644 --- a/services/java/com/android/server/updates/IntentFirewallInstallReceiver.java +++ b/services/java/com/android/server/updates/IntentFirewallInstallReceiver.java @@ -21,7 +21,8 @@ import com.android.server.firewall.IntentFirewall; public class IntentFirewallInstallReceiver extends ConfigUpdateInstallReceiver { public IntentFirewallInstallReceiver() { - super(IntentFirewall.getRulesFile().getParent(), IntentFirewall.getRulesFile().getName(), - "metadata/", "version"); + // TODO: should we dynamically generate a filename and store the name in metadata? + super(IntentFirewall.getRulesDir().getAbsolutePath(), "ifw.xml", "metadata/", + "gservices.version"); } } diff --git a/services/java/com/android/server/usb/UsbDebuggingManager.java b/services/java/com/android/server/usb/UsbDebuggingManager.java index 93d31144815f..ce953a4b134e 100644 --- a/services/java/com/android/server/usb/UsbDebuggingManager.java +++ b/services/java/com/android/server/usb/UsbDebuggingManager.java @@ -22,15 +22,14 @@ import android.content.Intent; import android.net.LocalSocket; import android.net.LocalSocketAddress; import android.os.Handler; -import android.os.HandlerThread; import android.os.Environment; import android.os.FileUtils; import android.os.Looper; import android.os.Message; -import android.os.Process; import android.os.SystemClock; import android.util.Slog; import android.util.Base64; +import com.android.server.FgThread; import java.lang.Thread; import java.io.File; @@ -54,7 +53,6 @@ public class UsbDebuggingManager implements Runnable { private final Context mContext; private final Handler mHandler; - private final HandlerThread mHandlerThread; private Thread mThread; private boolean mAdbEnabled = false; private String mFingerprints; @@ -62,9 +60,7 @@ public class UsbDebuggingManager implements Runnable { private OutputStream mOutputStream = null; public UsbDebuggingManager(Context context) { - mHandlerThread = new HandlerThread("UsbDebuggingHandler"); - mHandlerThread.start(); - mHandler = new UsbDebuggingHandler(mHandlerThread.getLooper()); + mHandler = new UsbDebuggingHandler(FgThread.get().getLooper()); mContext = context; } @@ -165,7 +161,7 @@ public class UsbDebuggingManager implements Runnable { mAdbEnabled = true; - mThread = new Thread(UsbDebuggingManager.this); + mThread = new Thread(UsbDebuggingManager.this, TAG); mThread.start(); break; diff --git a/services/java/com/android/server/usb/UsbDeviceManager.java b/services/java/com/android/server/usb/UsbDeviceManager.java index 87aa8cce166a..5a60de0f269b 100644 --- a/services/java/com/android/server/usb/UsbDeviceManager.java +++ b/services/java/com/android/server/usb/UsbDeviceManager.java @@ -32,11 +32,9 @@ import android.hardware.usb.UsbAccessory; import android.hardware.usb.UsbManager; import android.os.FileUtils; import android.os.Handler; -import android.os.HandlerThread; import android.os.Looper; import android.os.Message; import android.os.ParcelFileDescriptor; -import android.os.Process; import android.os.SystemClock; import android.os.SystemProperties; import android.os.UEventObserver; @@ -48,6 +46,7 @@ import android.util.Pair; import android.util.Slog; import com.android.internal.annotations.GuardedBy; +import com.android.server.FgThread; import java.io.File; import java.io.FileDescriptor; @@ -57,6 +56,7 @@ import java.io.PrintWriter; import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.Scanner; @@ -158,11 +158,7 @@ public class UsbDeviceManager { readOemUsbOverrideConfig(); - // create a thread for our Handler - HandlerThread thread = new HandlerThread("UsbDeviceManager", - Process.THREAD_PRIORITY_BACKGROUND); - thread.start(); - mHandler = new UsbHandler(thread.getLooper()); + mHandler = new UsbHandler(FgThread.get().getLooper()); if (nativeIsStartRequested()) { if (DEBUG) Slog.d(TAG, "accessory attached at boot"); @@ -245,7 +241,7 @@ public class UsbDeviceManager { for (int i = 0; i < serialLength; i++) { address[i % (ETH_ALEN - 1) + 1] ^= (int)serial.charAt(i); } - String addrString = String.format("%02X:%02X:%02X:%02X:%02X:%02X", + String addrString = String.format(Locale.US, "%02X:%02X:%02X:%02X:%02X:%02X", address[0], address[1], address[2], address[3], address[4], address[5]); try { FileUtils.stringToFile(RNDIS_ETH_ADDR_PATH, addrString); diff --git a/services/java/com/android/server/wifi/WifiController.java b/services/java/com/android/server/wifi/WifiController.java index 87b43946a73a..a3d514e304ca 100644 --- a/services/java/com/android/server/wifi/WifiController.java +++ b/services/java/com/android/server/wifi/WifiController.java @@ -57,6 +57,7 @@ class WifiController extends StateMachine { private int mStayAwakeConditions; private long mIdleMillis; private int mSleepPolicy; + private boolean mFirstUserSignOnSeen = false; private AlarmManager mAlarmManager; private PendingIntent mIdleIntent; @@ -113,6 +114,7 @@ class WifiController extends StateMachine { static final int CMD_AIRPLANE_TOGGLED = BASE + 9; static final int CMD_SET_AP = BASE + 10; static final int CMD_DEFERRED_TOGGLE = BASE + 11; + static final int CMD_USER_PRESENT = BASE + 12; private DefaultState mDefaultState = new DefaultState(); private StaEnabledState mStaEnabledState = new StaEnabledState(); @@ -361,6 +363,9 @@ class WifiController extends StateMachine { case CMD_AIRPLANE_TOGGLED: case CMD_EMERGENCY_MODE_CHANGED: break; + case CMD_USER_PRESENT: + mFirstUserSignOnSeen = true; + break; case CMD_DEFERRED_TOGGLE: log("DEFERRED_TOGGLE ignored due to state change"); break; @@ -639,6 +644,15 @@ class WifiController extends StateMachine { if (msg.what == CMD_DEVICE_IDLE) { checkLocksAndTransitionWhenDeviceIdle(); // We let default state handle the rest of work + } else if (msg.what == CMD_USER_PRESENT) { + // TLS networks can't connect until user unlocks keystore. KeyStore + // unlocks when the user punches PIN after the reboot. So use this + // trigger to get those networks connected. + if (mFirstUserSignOnSeen == false) { + mWifiStateMachine.reloadTlsNetworksAndReconnect(); + } + mFirstUserSignOnSeen = true; + return HANDLED; } return NOT_HANDLED; } diff --git a/services/java/com/android/server/wifi/WifiService.java b/services/java/com/android/server/wifi/WifiService.java index a70978ee6c4b..86c68f31a00c 100644 --- a/services/java/com/android/server/wifi/WifiService.java +++ b/services/java/com/android/server/wifi/WifiService.java @@ -25,18 +25,20 @@ import android.content.Intent; import android.content.IntentFilter; import android.content.pm.PackageManager; import android.database.ContentObserver; +import android.net.DhcpInfo; +import android.net.DhcpResults; +import android.net.LinkAddress; +import android.net.NetworkUtils; +import android.net.RouteInfo; import android.net.wifi.IWifiManager; import android.net.wifi.ScanResult; +import android.net.wifi.BatchedScanResult; +import android.net.wifi.BatchedScanSettings; +import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.net.wifi.WifiStateMachine; -import android.net.wifi.WifiConfiguration; import android.net.wifi.WifiWatchdogStateMachine; -import android.net.DhcpInfo; -import android.net.DhcpResults; -import android.net.LinkAddress; -import android.net.NetworkUtils; -import android.net.RouteInfo; import android.os.Binder; import android.os.Handler; import android.os.Messenger; @@ -48,16 +50,24 @@ import android.os.RemoteException; import android.os.SystemProperties; import android.os.UserHandle; import android.os.WorkSource; +import android.os.AsyncTask; import android.provider.Settings; import android.util.Log; import android.util.Slog; +import java.io.FileNotFoundException; +import java.io.BufferedReader; import java.io.FileDescriptor; +import java.io.FileReader; +import java.io.IOException; import java.io.PrintWriter; + import java.net.InetAddress; import java.net.Inet4Address; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; + import java.util.concurrent.atomic.AtomicBoolean; import com.android.internal.R; @@ -73,6 +83,7 @@ import static com.android.server.wifi.WifiController.CMD_SCAN_ALWAYS_MODE_CHANGE import static com.android.server.wifi.WifiController.CMD_SCREEN_OFF; import static com.android.server.wifi.WifiController.CMD_SCREEN_ON; import static com.android.server.wifi.WifiController.CMD_SET_AP; +import static com.android.server.wifi.WifiController.CMD_USER_PRESENT; import static com.android.server.wifi.WifiController.CMD_WIFI_TOGGLED; /** * WifiService handles remote WiFi operation requests by implementing @@ -114,6 +125,8 @@ public final class WifiService extends IWifiManager.Stub { /* Tracks the persisted states for wi-fi & airplane mode */ final WifiSettingsStore mSettingsStore; + final boolean mBatchedScanSupported; + /** * Asynchronous channel to WifiStateMachine */ @@ -158,7 +171,26 @@ public final class WifiService extends IWifiManager.Stub { } /* Client commands are forwarded to state machine */ case WifiManager.CONNECT_NETWORK: - case WifiManager.SAVE_NETWORK: + case WifiManager.SAVE_NETWORK: { + WifiConfiguration config = (WifiConfiguration) msg.obj; + int networkId = msg.arg1; + if (config != null && config.isValid()) { + if (DBG) Slog.d(TAG, "Connect with config" + config); + mWifiStateMachine.sendMessage(Message.obtain(msg)); + } else if (config == null + && networkId != WifiConfiguration.INVALID_NETWORK_ID) { + if (DBG) Slog.d(TAG, "Connect with networkId" + networkId); + mWifiStateMachine.sendMessage(Message.obtain(msg)); + } else { + Slog.e(TAG, "ClientHandler.handleMessage ignoring invalid msg=" + msg); + if (msg.what == WifiManager.CONNECT_NETWORK) { + replyFailed(msg, WifiManager.CONNECT_NETWORK_FAILED); + } else { + replyFailed(msg, WifiManager.SAVE_NETWORK_FAILED); + } + } + break; + } case WifiManager.FORGET_NETWORK: case WifiManager.START_WPS: case WifiManager.CANCEL_WPS: @@ -173,6 +205,17 @@ public final class WifiService extends IWifiManager.Stub { } } } + + private void replyFailed(Message msg, int what) { + Message reply = msg.obtain(); + reply.what = what; + reply.arg1 = WifiManager.INVALID_ARGS; + try { + msg.replyTo.send(reply); + } catch (RemoteException e) { + // There's not much we can do if reply can't be sent! + } + } } private ClientHandler mClientHandler; @@ -239,6 +282,9 @@ public final class WifiService extends IWifiManager.Stub { mWifiController = new WifiController(mContext, this, wifiThread.getLooper()); mWifiController.start(); + mBatchedScanSupported = mContext.getResources().getBoolean( + R.bool.config_wifi_batched_scan_supported); + registerForScanModeChange(); mContext.registerReceiver( new BroadcastReceiver() { @@ -296,10 +342,164 @@ public final class WifiService extends IWifiManager.Stub { /** * see {@link android.net.wifi.WifiManager#startScan()} + * + * <p>If workSource is null, all blame is given to the calling uid. + */ + public void startScan(WorkSource workSource) { + enforceChangePermission(); + if (workSource != null) { + enforceWorkSourcePermission(); + // WifiManager currently doesn't use names, so need to clear names out of the + // supplied WorkSource to allow future WorkSource combining. + workSource.clearNames(); + } + mWifiStateMachine.startScan(Binder.getCallingUid(), workSource); + } + + private class BatchedScanRequest extends DeathRecipient { + BatchedScanSettings settings; + int uid; + int pid; + + BatchedScanRequest(BatchedScanSettings settings, IBinder binder) { + super(0, null, binder, null); + this.settings = settings; + this.uid = getCallingUid(); + this.pid = getCallingPid(); + } + public void binderDied() { + stopBatchedScan(settings, uid, pid); + } + public String toString() { + return "BatchedScanRequest{settings=" + settings + ", binder=" + mBinder + "}"; + } + + public boolean isSameApp(int uid, int pid) { + return (this.uid == uid && this.pid == pid); + } + } + + private final List<BatchedScanRequest> mBatchedScanners = new ArrayList<BatchedScanRequest>(); + + public boolean isBatchedScanSupported() { + return mBatchedScanSupported; + } + + public void pollBatchedScan() { + enforceChangePermission(); + if (mBatchedScanSupported == false) return; + mWifiStateMachine.requestBatchedScanPoll(); + } + + /** + * see {@link android.net.wifi.WifiManager#requestBatchedScan()} */ - public void startScan() { + public boolean requestBatchedScan(BatchedScanSettings requested, IBinder binder) { + enforceChangePermission(); + if (mBatchedScanSupported == false) return false; + requested = new BatchedScanSettings(requested); + if (requested.isInvalid()) return false; + BatchedScanRequest r = new BatchedScanRequest(requested, binder); + synchronized(mBatchedScanners) { + mBatchedScanners.add(r); + resolveBatchedScannersLocked(); + } + return true; + } + + public List<BatchedScanResult> getBatchedScanResults(String callingPackage) { + enforceAccessPermission(); + if (mBatchedScanSupported == false) return new ArrayList<BatchedScanResult>(); + int userId = UserHandle.getCallingUserId(); + int uid = Binder.getCallingUid(); + long ident = Binder.clearCallingIdentity(); + try { + if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage) + != AppOpsManager.MODE_ALLOWED) { + return new ArrayList<BatchedScanResult>(); + } + int currentUser = ActivityManager.getCurrentUser(); + if (userId != currentUser) { + return new ArrayList<BatchedScanResult>(); + } else { + return mWifiStateMachine.syncGetBatchedScanResultsList(); + } + } finally { + Binder.restoreCallingIdentity(ident); + } + } + + + public void stopBatchedScan(BatchedScanSettings settings) { enforceChangePermission(); - mWifiStateMachine.startScan(Binder.getCallingUid()); + if (mBatchedScanSupported == false) return; + stopBatchedScan(settings, getCallingUid(), getCallingPid()); + } + + private void stopBatchedScan(BatchedScanSettings settings, int uid, int pid) { + ArrayList<BatchedScanRequest> found = new ArrayList<BatchedScanRequest>(); + synchronized(mBatchedScanners) { + for (BatchedScanRequest r : mBatchedScanners) { + if (r.isSameApp(uid, pid) && (settings == null || settings.equals(r.settings))) { + found.add(r); + if (settings != null) break; + } + } + for (BatchedScanRequest r : found) { + mBatchedScanners.remove(r); + } + if (found.size() != 0) { + resolveBatchedScannersLocked(); + } + } + } + + private void resolveBatchedScannersLocked() { + BatchedScanSettings setting = new BatchedScanSettings(); + int responsibleUid = 0; + + if (mBatchedScanners.size() == 0) { + mWifiStateMachine.setBatchedScanSettings(null, 0); + return; + } + for (BatchedScanRequest r : mBatchedScanners) { + BatchedScanSettings s = r.settings; + if (s.maxScansPerBatch != BatchedScanSettings.UNSPECIFIED && + s.maxScansPerBatch < setting.maxScansPerBatch) { + setting.maxScansPerBatch = s.maxScansPerBatch; + responsibleUid = r.uid; + } + if (s.maxApPerScan != BatchedScanSettings.UNSPECIFIED && + (setting.maxApPerScan == BatchedScanSettings.UNSPECIFIED || + s.maxApPerScan > setting.maxApPerScan)) { + setting.maxApPerScan = s.maxApPerScan; + } + if (s.scanIntervalSec != BatchedScanSettings.UNSPECIFIED && + s.scanIntervalSec < setting.scanIntervalSec) { + setting.scanIntervalSec = s.scanIntervalSec; + responsibleUid = r.uid; + } + if (s.maxApForDistance != BatchedScanSettings.UNSPECIFIED && + (setting.maxApForDistance == BatchedScanSettings.UNSPECIFIED || + s.maxApForDistance > setting.maxApForDistance)) { + setting.maxApForDistance = s.maxApForDistance; + } + if (s.channelSet != null && s.channelSet.size() != 0) { + if (setting.channelSet == null || setting.channelSet.size() != 0) { + if (setting.channelSet == null) setting.channelSet = new ArrayList<String>(); + for (String i : s.channelSet) { + if (setting.channelSet.contains(i) == false) setting.channelSet.add(i); + } + } // else, ignore the constraint - we already use all channels + } else { + if (setting.channelSet == null || setting.channelSet.size() != 0) { + setting.channelSet = new ArrayList<String>(); + } + } + } + + setting.constrain(); + mWifiStateMachine.setBatchedScanSettings(setting, responsibleUid); } private void enforceAccessPermission() { @@ -313,6 +513,12 @@ public final class WifiService extends IWifiManager.Stub { } + private void enforceWorkSourcePermission() { + mContext.enforceCallingPermission(android.Manifest.permission.UPDATE_DEVICE_STATS, + "WifiService"); + + } + private void enforceMulticastChangePermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CHANGE_WIFI_MULTICAST_STATE, @@ -379,7 +585,12 @@ public final class WifiService extends IWifiManager.Stub { */ public void setWifiApEnabled(WifiConfiguration wifiConfig, boolean enabled) { enforceChangePermission(); - mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget(); + // null wifiConfig is a meaningful input for CMD_SET_AP + if (wifiConfig == null || wifiConfig.isValid()) { + mWifiController.obtainMessage(CMD_SET_AP, enabled ? 1 : 0, 0, wifiConfig).sendToTarget(); + } else { + Slog.e(TAG, "Invalid WifiConfiguration"); + } } /** @@ -412,7 +623,11 @@ public final class WifiService extends IWifiManager.Stub { enforceChangePermission(); if (wifiConfig == null) return; - mWifiStateMachine.setWifiApConfiguration(wifiConfig); + if (wifiConfig.isValid()) { + mWifiStateMachine.setWifiApConfiguration(wifiConfig); + } else { + Slog.e(TAG, "Invalid WifiConfiguration"); + } } /** @@ -470,10 +685,15 @@ public final class WifiService extends IWifiManager.Stub { */ public int addOrUpdateNetwork(WifiConfiguration config) { enforceChangePermission(); - if (mWifiStateMachineChannel != null) { - return mWifiStateMachine.syncAddOrUpdateNetwork(mWifiStateMachineChannel, config); + if (config.isValid()) { + if (mWifiStateMachineChannel != null) { + return mWifiStateMachine.syncAddOrUpdateNetwork(mWifiStateMachineChannel, config); + } else { + Slog.e(TAG, "mWifiStateMachineChannel is not initialized"); + return -1; + } } else { - Slog.e(TAG, "mWifiStateMachineChannel is not initialized"); + Slog.e(TAG, "bad network configuration"); return -1; } } @@ -551,11 +771,11 @@ public final class WifiService extends IWifiManager.Stub { int userId = UserHandle.getCallingUserId(); int uid = Binder.getCallingUid(); long ident = Binder.clearCallingIdentity(); - if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage) - != AppOpsManager.MODE_ALLOWED) { - return new ArrayList<ScanResult>(); - } try { + if (mAppOps.noteOp(AppOpsManager.OP_WIFI_SCAN, uid, callingPackage) + != AppOpsManager.MODE_ALLOWED) { + return new ArrayList<ScanResult>(); + } int currentUser = ActivityManager.getCurrentUser(); if (userId != currentUser) { return new ArrayList<ScanResult>(); @@ -749,6 +969,92 @@ public final class WifiService extends IWifiManager.Stub { } /** + * enable TDLS for the local NIC to remote NIC + * The APPs don't know the remote MAC address to identify NIC though, + * so we need to do additional work to find it from remote IP address + */ + + class TdlsTaskParams { + public String remoteIpAddress; + public boolean enable; + } + + class TdlsTask extends AsyncTask<TdlsTaskParams, Integer, Integer> { + @Override + protected Integer doInBackground(TdlsTaskParams... params) { + + // Retrieve parameters for the call + TdlsTaskParams param = params[0]; + String remoteIpAddress = param.remoteIpAddress.trim(); + boolean enable = param.enable; + + // Get MAC address of Remote IP + String macAddress = null; + + BufferedReader reader = null; + + try { + reader = new BufferedReader(new FileReader("/proc/net/arp")); + + // Skip over the line bearing colum titles + String line = reader.readLine(); + + while ((line = reader.readLine()) != null) { + String[] tokens = line.split("[ ]+"); + if (tokens.length < 6) { + continue; + } + + // ARP column format is + // Address HWType HWAddress Flags Mask IFace + String ip = tokens[0]; + String mac = tokens[3]; + + if (remoteIpAddress.equals(ip)) { + macAddress = mac; + break; + } + } + + if (macAddress == null) { + Slog.w(TAG, "Did not find remoteAddress {" + remoteIpAddress + "} in " + + "/proc/net/arp"); + } else { + enableTdlsWithMacAddress(macAddress, enable); + } + + } catch (FileNotFoundException e) { + Slog.e(TAG, "Could not open /proc/net/arp to lookup mac address"); + } catch (IOException e) { + Slog.e(TAG, "Could not read /proc/net/arp to lookup mac address"); + } finally { + try { + if (reader != null) { + reader.close(); + } + } + catch (IOException e) { + // Do nothing + } + } + + return 0; + } + } + + public void enableTdls(String remoteAddress, boolean enable) { + TdlsTaskParams params = new TdlsTaskParams(); + params.remoteIpAddress = remoteAddress; + params.enable = enable; + new TdlsTask().execute(params); + } + + + public void enableTdlsWithMacAddress(String remoteMacAddress, boolean enable) { + mWifiStateMachine.enableTdls(remoteMacAddress, enable); + } + + /** * Get a reference to handler. This is used by a client to establish * an AsyncChannel communication with WifiService */ @@ -779,6 +1085,8 @@ public final class WifiService extends IWifiManager.Stub { String action = intent.getAction(); if (action.equals(Intent.ACTION_SCREEN_ON)) { mWifiController.sendMessage(CMD_SCREEN_ON); + } else if (action.equals(Intent.ACTION_USER_PRESENT)) { + mWifiController.sendMessage(CMD_USER_PRESENT); } else if (action.equals(Intent.ACTION_SCREEN_OFF)) { mWifiController.sendMessage(CMD_SCREEN_OFF); } else if (action.equals(Intent.ACTION_BATTERY_CHANGED)) { @@ -815,6 +1123,7 @@ public final class WifiService extends IWifiManager.Stub { private void registerForBroadcasts() { IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_SCREEN_ON); + intentFilter.addAction(Intent.ACTION_USER_PRESENT); intentFilter.addAction(Intent.ACTION_SCREEN_OFF); intentFilter.addAction(Intent.ACTION_BATTERY_CHANGED); intentFilter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION); diff --git a/services/java/com/android/server/wm/AppTransition.java b/services/java/com/android/server/wm/AppTransition.java index cd3daaa9f271..756e06a68d87 100644 --- a/services/java/com/android/server/wm/AppTransition.java +++ b/services/java/com/android/server/wm/AppTransition.java @@ -722,7 +722,7 @@ public class AppTransition implements Dump { @Override public void dump(PrintWriter pw) { pw.print(" " + this); - pw.print(" mAppTransitionState="); pw.println(appStateToString()); + pw.print(" mAppTransitionState="); pw.println(appStateToString()); if (mNextAppTransitionType != NEXT_TRANSIT_TYPE_NONE) { pw.print(" mNextAppTransitionType="); pw.println(transitTypeToString()); } diff --git a/services/java/com/android/server/wm/AppWindowAnimator.java b/services/java/com/android/server/wm/AppWindowAnimator.java index 6293dc647df1..3cccf1d534f2 100644 --- a/services/java/com/android/server/wm/AppWindowAnimator.java +++ b/services/java/com/android/server/wm/AppWindowAnimator.java @@ -6,7 +6,6 @@ import android.graphics.Matrix; import android.util.Slog; import android.util.TimeUtils; import android.view.Display; -import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowManagerPolicy; import android.view.animation.Animation; diff --git a/services/java/com/android/server/wm/AppWindowToken.java b/services/java/com/android/server/wm/AppWindowToken.java index fbb501397163..8cc1d0211132 100644 --- a/services/java/com/android/server/wm/AppWindowToken.java +++ b/services/java/com/android/server/wm/AppWindowToken.java @@ -30,6 +30,10 @@ import android.view.View; import android.view.WindowManager; import java.io.PrintWriter; +import java.util.ArrayList; + +class AppTokenList extends ArrayList<AppWindowToken> { +} /** * Version of WindowToken that is specifically for a particular application (or diff --git a/services/java/com/android/server/wm/BlackFrame.java b/services/java/com/android/server/wm/BlackFrame.java index 774b1652434f..5aa266d473bd 100644 --- a/services/java/com/android/server/wm/BlackFrame.java +++ b/services/java/com/android/server/wm/BlackFrame.java @@ -22,7 +22,7 @@ import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Rect; import android.util.Slog; -import android.view.Surface; +import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -37,7 +37,7 @@ public class BlackFrame { final SurfaceControl surface; BlackSurface(SurfaceSession session, int layer, int l, int t, int r, int b, int layerStack) - throws SurfaceControl.OutOfResourcesException { + throws OutOfResourcesException { left = l; top = t; this.layer = layer; @@ -62,6 +62,10 @@ public class BlackFrame { " BLACK " + surface + ": CREATE layer=" + layer); } + void setAlpha(float alpha) { + surface.setAlpha(alpha); + } + void setMatrix(Matrix matrix) { mTmpMatrix.setTranslate(left, top); mTmpMatrix.postConcat(matrix); @@ -93,6 +97,8 @@ public class BlackFrame { final float[] mTmpFloats = new float[9]; final BlackSurface[] mBlackSurfaces = new BlackSurface[4]; + final boolean mForceDefaultOrientation; + public void printTo(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("Outer: "); mOuterRect.printShortString(pw); pw.print(" / Inner: "); mInnerRect.printShortString(pw); @@ -106,10 +112,12 @@ public class BlackFrame { } } - public BlackFrame(SurfaceSession session, Rect outer, Rect inner, - int layer, final int layerStack) throws SurfaceControl.OutOfResourcesException { + public BlackFrame(SurfaceSession session, Rect outer, Rect inner, int layer, int layerStack, + boolean forceDefaultOrientation) throws OutOfResourcesException { boolean success = false; + mForceDefaultOrientation = forceDefaultOrientation; + mOuterRect = new Rect(outer); mInnerRect = new Rect(inner); try { @@ -162,6 +170,14 @@ public class BlackFrame { } } + public void setAlpha(float alpha) { + for (int i=0; i<mBlackSurfaces.length; i++) { + if (mBlackSurfaces[i] != null) { + mBlackSurfaces[i].setAlpha(alpha); + } + } + } + public void setMatrix(Matrix matrix) { for (int i=0; i<mBlackSurfaces.length; i++) { if (mBlackSurfaces[i] != null) { diff --git a/services/java/com/android/server/wm/DimLayer.java b/services/java/com/android/server/wm/DimLayer.java index 9bd36ba0f22f..c189ddd86c14 100644 --- a/services/java/com/android/server/wm/DimLayer.java +++ b/services/java/com/android/server/wm/DimLayer.java @@ -3,10 +3,10 @@ package com.android.server.wm; import android.graphics.PixelFormat; +import android.graphics.Rect; import android.os.SystemClock; import android.util.Slog; import android.view.DisplayInfo; -import android.view.Surface; import android.view.SurfaceControl; import java.io.PrintWriter; @@ -27,8 +27,11 @@ public class DimLayer { /** Last value passed to mDimSurface.setLayer() */ int mLayer = -1; - /** Last values passed to mDimSurface.setSize() */ - int mLastDimWidth, mLastDimHeight; + /** Next values to pass to mDimSurface.setPosition() and mDimSurface.setSize() */ + Rect mBounds = new Rect(); + + /** Last values passed to mDimSurface.setPosition() and mDimSurface.setSize() */ + Rect mLastBounds = new Rect(); /** True after mDimSurface.show() has been called, false after mDimSurface.hide(). */ private boolean mShowing = false; @@ -45,9 +48,14 @@ public class DimLayer { /** Time in milliseconds to take to transition from mStartAlpha to mTargetAlpha */ long mDuration; - DimLayer(WindowManagerService service, int displayId) { + /** Owning stack */ + final TaskStack mStack; + + DimLayer(WindowManagerService service, TaskStack stack) { + mStack = stack; + mDisplayContent = stack.getDisplayContent(); + final int displayId = mDisplayContent.getDisplayId(); if (DEBUG) Slog.v(TAG, "Ctor: displayId=" + displayId); - mDisplayContent = service.getDisplayContentLocked(displayId); SurfaceControl.openTransaction(); try { if (WindowManagerService.DEBUG_SURFACE_TRACE) { @@ -117,6 +125,10 @@ public class DimLayer { } } + void setBounds(Rect bounds) { + mBounds.set(bounds); + } + /** * @param duration The time to test. * @return True if the duration would lead to an earlier end to the current animation. @@ -152,17 +164,26 @@ public class DimLayer { return; } - // Set surface size to screen size. - final DisplayInfo info = mDisplayContent.getDisplayInfo(); - // Multiply by 1.5 so that rotating a frozen surface that includes this does not expose a - // corner. - final int dw = (int) (info.logicalWidth * 1.5); - final int dh = (int) (info.logicalHeight * 1.5); - // back off position so 1/4 of Surface is before and 1/4 is after. - final float xPos = -1 * dw / 6; - final float yPos = -1 * dh / 6; - - if (mLastDimWidth != dw || mLastDimHeight != dh || mLayer != layer) { + final int dw, dh; + final float xPos, yPos; + if (mStack.hasSibling()) { + dw = mBounds.width(); + dh = mBounds.height(); + xPos = mBounds.left; + yPos = mBounds.right; + } else { + // Set surface size to screen size. + final DisplayInfo info = mDisplayContent.getDisplayInfo(); + // Multiply by 1.5 so that rotating a frozen surface that includes this does not expose a + // corner. + dw = (int) (info.logicalWidth * 1.5); + dh = (int) (info.logicalHeight * 1.5); + // back off position so 1/4 of Surface is before and 1/4 is after. + xPos = -1 * dw / 6; + yPos = -1 * dh / 6; + } + + if (!mLastBounds.equals(mBounds) || mLayer != layer) { try { mDimSurface.setPosition(xPos, yPos); mDimSurface.setSize(dw, dh); @@ -170,8 +191,7 @@ public class DimLayer { } catch (RuntimeException e) { Slog.w(TAG, "Failure setting size or layer", e); } - mLastDimWidth = dw; - mLastDimHeight = dh; + mLastBounds.set(mBounds); mLayer = layer; } @@ -255,15 +275,16 @@ public class DimLayer { } public void printTo(String prefix, PrintWriter pw) { - pw.print(prefix); pw.print("mDimSurface="); pw.println(mDimSurface); - pw.print(prefix); pw.print(" mLayer="); pw.print(mLayer); + pw.print(prefix); pw.print("mDimSurface="); pw.print(mDimSurface); + pw.print(" mLayer="); pw.print(mLayer); pw.print(" mAlpha="); pw.println(mAlpha); - pw.print(prefix); pw.print("mLastDimWidth="); pw.print(mLastDimWidth); - pw.print(" mLastDimWidth="); pw.println(mLastDimWidth); - pw.print(prefix); pw.print("Last animation: mStartTime="); pw.print(mStartTime); + pw.print(prefix); pw.print("mLastBounds="); pw.print(mLastBounds.toShortString()); + pw.print(" mBounds="); pw.println(mBounds.toShortString()); + pw.print(prefix); pw.print("Last animation: "); pw.print(" mDuration="); pw.print(mDuration); + pw.print(" mStartTime="); pw.print(mStartTime); pw.print(" curTime="); pw.println(SystemClock.uptimeMillis()); - pw.print(" mStartAlpha="); pw.println(mStartAlpha); - pw.print(" mTargetAlpha="); pw.print(mTargetAlpha); + pw.print(prefix); pw.print(" mStartAlpha="); pw.print(mStartAlpha); + pw.print(" mTargetAlpha="); pw.println(mTargetAlpha); } } diff --git a/services/java/com/android/server/wm/DisplayContent.java b/services/java/com/android/server/wm/DisplayContent.java index 59e4b0e6f730..afa4f78b51de 100644 --- a/services/java/com/android/server/wm/DisplayContent.java +++ b/services/java/com/android/server/wm/DisplayContent.java @@ -16,6 +16,16 @@ package com.android.server.wm; +import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; +import static com.android.server.wm.WindowManagerService.DEBUG_STACK; +import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY; +import static com.android.server.wm.WindowManagerService.TAG; + +import android.app.ActivityManager.StackBoxInfo; +import android.graphics.Rect; +import android.graphics.Region; +import android.os.Debug; +import android.util.Slog; import android.view.Display; import android.view.DisplayInfo; @@ -61,19 +71,66 @@ class DisplayContent { private final DisplayInfo mDisplayInfo = new DisplayInfo(); private final Display mDisplay; + Rect mBaseDisplayRect = new Rect(); + // Accessed directly by all users. boolean layoutNeeded; int pendingLayoutChanges; final boolean isDefaultDisplay; /** + * Window tokens that are in the process of exiting, but still + * on screen for animations. + */ + final ArrayList<WindowToken> mExitingTokens = new ArrayList<WindowToken>(); + + /** + * Application tokens that are in the process of exiting, but still + * on screen for animations. + */ + final AppTokenList mExitingAppTokens = new AppTokenList(); + + /** Array containing the home StackBox and possibly one more which would contain apps. Array + * is stored in display order with the current bottom stack at 0. */ + private ArrayList<StackBox> mStackBoxes = new ArrayList<StackBox>(); + + /** True when the home StackBox is at the top of mStackBoxes, false otherwise. */ + private TaskStack mHomeStack = null; + + /** Sorted most recent at top, oldest at [0]. */ + ArrayList<TaskStack> mStackHistory = new ArrayList<TaskStack>(); + + /** Detect user tapping outside of current focused stack bounds .*/ + StackTapPointerEventListener mTapDetector; + + /** Detect user tapping outside of current focused stack bounds .*/ + Region mTouchExcludeRegion = new Region(); + + /** Save allocating when retrieving tasks */ + ArrayList<Task> mTmpTasks = new ArrayList<Task>(); + + /** Save allocating when calculating rects */ + Rect mTmpRect = new Rect(); + + final WindowManagerService mService; + + /** * @param display May not be null. + * @param service TODO(cmautner): */ - DisplayContent(Display display) { + DisplayContent(Display display, WindowManagerService service) { mDisplay = display; mDisplayId = display.getDisplayId(); display.getDisplayInfo(mDisplayInfo); isDefaultDisplay = mDisplayId == Display.DEFAULT_DISPLAY; + mService = service; + + StackBox newBox = new StackBox(service, this, null); + mStackBoxes.add(newBox); + TaskStack newStack = new TaskStack(service, HOME_STACK_ID, this); + newStack.mStackBox = newBox; + newBox.mStack = newStack; + mHomeStack = newStack; } int getDisplayId() { @@ -92,10 +149,293 @@ class DisplayContent { return mDisplayInfo; } - public void updateDisplayInfo() { + /** + * Returns true if the specified UID has access to this display. + */ + public boolean hasAccess(int uid) { + return mDisplay.hasAccess(uid); + } + + boolean homeOnTop() { + return mStackBoxes.get(0).mStack != mHomeStack; + } + + void moveStack(TaskStack stack, boolean toTop) { + mStackHistory.remove(stack); + mStackHistory.add(toTop ? mStackHistory.size() : 0, stack); + mService.moveStackWindowsLocked(stack); + } + + public boolean isPrivate() { + return (mDisplay.getFlags() & Display.FLAG_PRIVATE) != 0; + } + + /** + * Retrieve the tasks on this display in stack order from the bottommost TaskStack up. + * @return All the Tasks, in order, on this display. + */ + ArrayList<Task> getTasks() { + mTmpTasks.clear(); + final int numStacks = mStackHistory.size(); + for (int stackNdx = 0; stackNdx < numStacks; ++stackNdx) { + mTmpTasks.addAll(mStackHistory.get(stackNdx).getTasks()); + } + if (WindowManagerService.DEBUG_LAYERS) Slog.i(TAG, "getTasks: mStackHistory=" + + mStackHistory); + return mTmpTasks; + } + + TaskStack getHomeStack() { + return mHomeStack; + } + + void updateDisplayInfo() { mDisplay.getDisplayInfo(mDisplayInfo); } + void getLogicalDisplayRect(Rect out) { + updateDisplayInfo(); + // Uses same calculation as in LogicalDisplay#configureDisplayInTransactionLocked. + int width = mDisplayInfo.logicalWidth; + int left = (mBaseDisplayWidth - width) / 2; + int height = mDisplayInfo.logicalHeight; + int top = (mBaseDisplayHeight - height) / 2; + out.set(left, top, left + width, top + height); + } + + /** @return The number of tokens in all of the Tasks on this display. */ + int numTokens() { + getTasks(); + int count = 0; + for (int taskNdx = mTmpTasks.size() - 1; taskNdx >= 0; --taskNdx) { + count += mTmpTasks.get(taskNdx).mAppTokens.size(); + } + return count; + } + + /** Refer to {@link WindowManagerService#createStack(int, int, int, float)} */ + TaskStack createStack(int stackId, int relativeStackBoxId, int position, float weight) { + TaskStack newStack = null; + if (DEBUG_STACK) Slog.d(TAG, "createStack: stackId=" + stackId + " relativeStackBoxId=" + + relativeStackBoxId + " position=" + position + " weight=" + weight); + if (stackId == HOME_STACK_ID) { + if (mStackBoxes.size() != 1) { + throw new IllegalArgumentException("createStack: HOME_STACK_ID (0) not first."); + } + newStack = mHomeStack; + } else { + int stackBoxNdx; + for (stackBoxNdx = mStackBoxes.size() - 1; stackBoxNdx >= 0; --stackBoxNdx) { + final StackBox box = mStackBoxes.get(stackBoxNdx); + if (position == StackBox.TASK_STACK_GOES_OVER + || position == StackBox.TASK_STACK_GOES_UNDER) { + // Position indicates a new box is added at top level only. + if (box.contains(relativeStackBoxId)) { + StackBox newBox = new StackBox(mService, this, null); + newStack = new TaskStack(mService, stackId, this); + newStack.mStackBox = newBox; + newBox.mStack = newStack; + final int offset = position == StackBox.TASK_STACK_GOES_OVER ? 1 : 0; + if (DEBUG_STACK) Slog.d(TAG, "createStack: inserting stack at " + + (stackBoxNdx + offset)); + mStackBoxes.add(stackBoxNdx + offset, newBox); + break; + } + } else { + // Remaining position values indicate a box must be split. + newStack = box.split(stackId, relativeStackBoxId, position, weight); + if (newStack != null) { + break; + } + } + } + if (stackBoxNdx < 0) { + throw new IllegalArgumentException("createStack: stackBoxId " + relativeStackBoxId + + " not found."); + } + } + if (newStack != null) { + layoutNeeded = true; + } + return newStack; + } + + /** Refer to {@link WindowManagerService#resizeStackBox(int, float)} */ + boolean resizeStack(int stackBoxId, float weight) { + for (int stackBoxNdx = mStackBoxes.size() - 1; stackBoxNdx >= 0; --stackBoxNdx) { + final StackBox box = mStackBoxes.get(stackBoxNdx); + if (box.resize(stackBoxId, weight)) { + layoutNeeded = true; + return true; + } + } + return false; + } + + void addStackBox(StackBox box, boolean toTop) { + if (mStackBoxes.size() >= 2) { + throw new RuntimeException("addStackBox: Too many toplevel StackBoxes!"); + } + mStackBoxes.add(toTop ? mStackBoxes.size() : 0, box); + } + + void removeStackBox(StackBox box) { + if (DEBUG_STACK) Slog.d(TAG, "removeStackBox: box=" + box); + final TaskStack stack = box.mStack; + if (stack != null && stack.mStackId == HOME_STACK_ID) { + // Never delete the home stack, even if it is empty. + if (DEBUG_STACK) Slog.d(TAG, "removeStackBox: Not deleting home stack."); + return; + } + mStackBoxes.remove(box); + } + + StackBoxInfo getStackBoxInfo(StackBox box) { + StackBoxInfo info = new StackBoxInfo(); + info.stackBoxId = box.mStackBoxId; + info.weight = box.mWeight; + info.vertical = box.mVertical; + info.bounds = new Rect(box.mBounds); + if (box.mStack != null) { + info.stackId = box.mStack.mStackId; + // ActivityManagerService will fill in the StackInfo. + } else { + info.stackId = -1; + info.children = new StackBoxInfo[2]; + info.children[0] = getStackBoxInfo(box.mFirst); + info.children[1] = getStackBoxInfo(box.mSecond); + } + return info; + } + + ArrayList<StackBoxInfo> getStackBoxInfos() { + ArrayList<StackBoxInfo> list = new ArrayList<StackBoxInfo>(); + for (int stackBoxNdx = mStackBoxes.size() - 1; stackBoxNdx >= 0; --stackBoxNdx) { + list.add(getStackBoxInfo(mStackBoxes.get(stackBoxNdx))); + } + return list; + } + + /** + * Move the home StackBox to the top or bottom of mStackBoxes. That is the only place + * it is allowed to be. This is a nop if the home StackBox is already in the correct position. + * @param toTop Move home to the top of mStackBoxes if true, to the bottom if false. + * @return true if a change was made, false otherwise. + */ + boolean moveHomeStackBox(boolean toTop) { + if (DEBUG_STACK) Slog.d(TAG, "moveHomeStackBox: toTop=" + toTop + " Callers=" + + Debug.getCallers(4)); + switch (mStackBoxes.size()) { + case 0: throw new RuntimeException("moveHomeStackBox: No home StackBox!"); + case 1: return false; // Only the home StackBox exists. + case 2: + if (homeOnTop() ^ toTop) { + mStackBoxes.add(mStackBoxes.remove(0)); + return true; + } + return false; + default: throw new RuntimeException("moveHomeStackBox: Too many toplevel StackBoxes!"); + } + } + + /** + * Propagate the new bounds to all child stack boxes, applying weights as we move down. + * @param contentRect The bounds to apply at the top level. + */ + boolean setStackBoxSize(Rect contentRect) { + boolean change = false; + for (int stackBoxNdx = mStackBoxes.size() - 1; stackBoxNdx >= 0; --stackBoxNdx) { + change |= mStackBoxes.get(stackBoxNdx).setStackBoxSizes(contentRect, true); + } + return change; + } + + Rect getStackBounds(int stackId) { + for (int stackBoxNdx = mStackBoxes.size() - 1; stackBoxNdx >= 0; --stackBoxNdx) { + Rect bounds = mStackBoxes.get(stackBoxNdx).getStackBounds(stackId); + if (bounds != null) { + return bounds; + } + } + return null; + } + + int stackIdFromPoint(int x, int y) { + StackBox topBox = mStackBoxes.get(mStackBoxes.size() - 1); + return topBox.stackIdFromPoint(x, y); + } + + void setTouchExcludeRegion(TaskStack focusedStack) { + mTouchExcludeRegion.set(mBaseDisplayRect); + WindowList windows = getWindowList(); + for (int i = windows.size() - 1; i >= 0; --i) { + final WindowState win = windows.get(i); + final TaskStack stack = win.getStack(); + if (win.isVisibleLw() && stack != null && stack != focusedStack) { + mTmpRect.set(win.mVisibleFrame); + mTmpRect.intersect(win.mVisibleInsets); + mTouchExcludeRegion.op(mTmpRect, Region.Op.DIFFERENCE); + } + } + } + + void switchUserStacks(int oldUserId, int newUserId) { + final WindowList windows = getWindowList(); + for (int i = 0; i < windows.size(); i++) { + final WindowState win = windows.get(i); + if (win.isHiddenFromUserLocked()) { + if (DEBUG_VISIBILITY) Slog.w(TAG, "user changing " + newUserId + " hiding " + + win + ", attrs=" + win.mAttrs.type + ", belonging to " + + win.mOwnerUid); + win.hideLw(false); + } + } + + for (int stackBoxNdx = mStackBoxes.size() - 1; stackBoxNdx >= 0; --stackBoxNdx) { + mStackBoxes.get(stackBoxNdx).switchUserStacks(newUserId); + } + } + + void resetAnimationBackgroundAnimator() { + for (int stackBoxNdx = mStackBoxes.size() - 1; stackBoxNdx >= 0; --stackBoxNdx) { + mStackBoxes.get(stackBoxNdx).resetAnimationBackgroundAnimator(); + } + } + + boolean animateDimLayers() { + boolean result = false; + for (int stackBoxNdx = mStackBoxes.size() - 1; stackBoxNdx >= 0; --stackBoxNdx) { + result |= mStackBoxes.get(stackBoxNdx).animateDimLayers(); + } + return result; + } + + void resetDimming() { + for (int stackBoxNdx = mStackBoxes.size() - 1; stackBoxNdx >= 0; --stackBoxNdx) { + mStackBoxes.get(stackBoxNdx).resetDimming(); + } + } + + boolean isDimming() { + boolean result = false; + for (int stackBoxNdx = mStackBoxes.size() - 1; stackBoxNdx >= 0; --stackBoxNdx) { + result |= mStackBoxes.get(stackBoxNdx).isDimming(); + } + return result; + } + + void stopDimmingIfNeeded() { + for (int stackBoxNdx = mStackBoxes.size() - 1; stackBoxNdx >= 0; --stackBoxNdx) { + mStackBoxes.get(stackBoxNdx).stopDimmingIfNeeded(); + } + } + + void close() { + for (int stackBoxNdx = mStackBoxes.size() - 1; stackBoxNdx >= 0; --stackBoxNdx) { + mStackBoxes.get(stackBoxNdx).close(); + } + } + public void dump(String prefix, PrintWriter pw) { pw.print(prefix); pw.print("Display: mDisplayId="); pw.println(mDisplayId); final String subPrefix = " " + prefix; @@ -119,7 +459,48 @@ class DisplayContent { pw.print("x"); pw.print(mDisplayInfo.smallestNominalAppHeight); pw.print("-"); pw.print(mDisplayInfo.largestNominalAppWidth); pw.print("x"); pw.println(mDisplayInfo.largestNominalAppHeight); - pw.print(subPrefix); pw.print("layoutNeeded="); pw.print(layoutNeeded); + pw.print(subPrefix); pw.print("layoutNeeded="); pw.println(layoutNeeded); + for (int boxNdx = 0; boxNdx < mStackBoxes.size(); ++boxNdx) { + pw.print(prefix); pw.print("StackBox #"); pw.println(boxNdx); + mStackBoxes.get(boxNdx).dump(prefix + " ", pw); + } + int ndx = numTokens(); + if (ndx > 0) { + pw.println(); + pw.println(" Application tokens in Z order:"); + getTasks(); + for (int taskNdx = mTmpTasks.size() - 1; taskNdx >= 0; --taskNdx) { + AppTokenList tokens = mTmpTasks.get(taskNdx).mAppTokens; + for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) { + final AppWindowToken wtoken = tokens.get(tokenNdx); + pw.print(" App #"); pw.print(ndx--); + pw.print(' '); pw.print(wtoken); pw.println(":"); + wtoken.dump(pw, " "); + } + } + } + if (mExitingTokens.size() > 0) { + pw.println(); + pw.println(" Exiting tokens:"); + for (int i=mExitingTokens.size()-1; i>=0; i--) { + WindowToken token = mExitingTokens.get(i); + pw.print(" Exiting #"); pw.print(i); + pw.print(' '); pw.print(token); + pw.println(':'); + token.dump(pw, " "); + } + } + if (mExitingAppTokens.size() > 0) { + pw.println(); + pw.println(" Exiting application tokens:"); + for (int i=mExitingAppTokens.size()-1; i>=0; i--) { + WindowToken token = mExitingAppTokens.get(i); + pw.print(" Exiting App #"); pw.print(i); + pw.print(' '); pw.print(token); + pw.println(':'); + token.dump(pw, " "); + } + } pw.println(); } } diff --git a/services/java/com/android/server/wm/DisplayMagnifier.java b/services/java/com/android/server/wm/DisplayMagnifier.java index 0f51028b99f9..382d7b4a94af 100644 --- a/services/java/com/android/server/wm/DisplayMagnifier.java +++ b/services/java/com/android/server/wm/DisplayMagnifier.java @@ -496,7 +496,7 @@ final class DisplayMagnifier { mWindowManager.getDefaultDisplay().getRealSize(mTempPoint); surfaceControl = new SurfaceControl(mWindowManagerService.mFxSession, SURFACE_TITLE, mTempPoint.x, mTempPoint.y, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); - } catch (SurfaceControl.OutOfResourcesException oore) { + } catch (OutOfResourcesException oore) { /* ignore */ } mSurfaceControl = surfaceControl; @@ -629,7 +629,7 @@ final class DisplayMagnifier { } } catch (IllegalArgumentException iae) { /* ignore */ - } catch (OutOfResourcesException oore) { + } catch (Surface.OutOfResourcesException oore) { /* ignore */ } if (canvas == null) { @@ -644,7 +644,7 @@ final class DisplayMagnifier { canvas.drawPath(path, mPaint); mSurface.unlockCanvasAndPost(canvas); - + if (mAlpha > 0) { mSurfaceControl.show(); } else { diff --git a/services/java/com/android/server/wm/DragState.java b/services/java/com/android/server/wm/DragState.java index 745b8860bb60..a73793968f49 100644 --- a/services/java/com/android/server/wm/DragState.java +++ b/services/java/com/android/server/wm/DragState.java @@ -115,6 +115,7 @@ class DragState { mDragWindowHandle.inputChannel = mServerChannel; mDragWindowHandle.layer = getDragLayerLw(); mDragWindowHandle.layoutParamsFlags = 0; + mDragWindowHandle.layoutParamsPrivateFlags = 0; mDragWindowHandle.layoutParamsType = WindowManager.LayoutParams.TYPE_DRAG; mDragWindowHandle.dispatchingTimeoutNanos = WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; diff --git a/services/java/com/android/server/wm/FakeWindowImpl.java b/services/java/com/android/server/wm/FakeWindowImpl.java index 5ec72cc3a818..5a3471b06215 100644 --- a/services/java/com/android/server/wm/FakeWindowImpl.java +++ b/services/java/com/android/server/wm/FakeWindowImpl.java @@ -40,8 +40,8 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { public FakeWindowImpl(WindowManagerService service, Looper looper, InputEventReceiver.Factory inputEventReceiverFactory, - String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, - boolean hasFocus, boolean touchFullscreen) { + String name, int windowType, int layoutParamsFlags, int layoutParamsPrivateFlags, + boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen) { mService = service; InputChannel[] channels = InputChannel.openInputChannelPair(name); @@ -63,6 +63,7 @@ public final class FakeWindowImpl implements WindowManagerPolicy.FakeWindow { mWindowLayer = getLayerLw(windowType); mWindowHandle.layer = mWindowLayer; mWindowHandle.layoutParamsFlags = layoutParamsFlags; + mWindowHandle.layoutParamsPrivateFlags = layoutParamsPrivateFlags; mWindowHandle.layoutParamsType = windowType; mWindowHandle.dispatchingTimeoutNanos = WindowManagerService.DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS; diff --git a/services/java/com/android/server/wm/FocusedStackFrame.java b/services/java/com/android/server/wm/FocusedStackFrame.java new file mode 100644 index 000000000000..cc48b867f6df --- /dev/null +++ b/services/java/com/android/server/wm/FocusedStackFrame.java @@ -0,0 +1,142 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.server.wm.WindowManagerService.DEBUG_STACK; +import static com.android.server.wm.WindowManagerService.DEBUG_SURFACE_TRACE; + +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.PixelFormat; +import android.graphics.Rect; +import android.graphics.Region; +import android.util.Slog; +import android.view.Display; +import android.view.Surface.OutOfResourcesException; +import android.view.Surface; +import android.view.SurfaceControl; +import android.view.SurfaceSession; + +import com.android.server.wm.WindowStateAnimator.SurfaceTrace; + +class FocusedStackFrame { + private static final String TAG = "FocusedStackFrame"; + private static final int THICKNESS = 10; + private static final float ALPHA = 0.3f; + + private final SurfaceControl mSurfaceControl; + private final Surface mSurface = new Surface(); + private final Rect mLastBounds = new Rect(); + private final Rect mBounds = new Rect(); + private final Rect mTmpDrawRect = new Rect(); + + public FocusedStackFrame(Display display, SurfaceSession session) { + SurfaceControl ctrl = null; + try { + if (DEBUG_SURFACE_TRACE) { + ctrl = new SurfaceTrace(session, "FocusedStackFrame", + 1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); + } else { + ctrl = new SurfaceControl(session, "FocusedStackFrame", + 1, 1, PixelFormat.TRANSLUCENT, SurfaceControl.HIDDEN); + } + ctrl.setLayerStack(display.getLayerStack()); + ctrl.setAlpha(ALPHA); + mSurface.copyFrom(ctrl); + } catch (OutOfResourcesException e) { + } + mSurfaceControl = ctrl; + } + + private void draw(Rect bounds, int color) { + if (false && DEBUG_STACK) Slog.i(TAG, "draw: bounds=" + bounds.toShortString() + + " color=" + Integer.toHexString(color)); + mTmpDrawRect.set(bounds); + Canvas c = null; + try { + c = mSurface.lockCanvas(mTmpDrawRect); + } catch (IllegalArgumentException e) { + } catch (Surface.OutOfResourcesException e) { + } + if (c == null) { + return; + } + + final int w = bounds.width(); + final int h = bounds.height(); + + // Top + mTmpDrawRect.set(0, 0, w, THICKNESS); + c.clipRect(mTmpDrawRect, Region.Op.REPLACE); + c.drawColor(color); + // Left (not including Top or Bottom stripe). + mTmpDrawRect.set(0, THICKNESS, THICKNESS, h - THICKNESS); + c.clipRect(mTmpDrawRect, Region.Op.REPLACE); + c.drawColor(color); + // Right (not including Top or Bottom stripe). + mTmpDrawRect.set(w - THICKNESS, THICKNESS, w, h - THICKNESS); + c.clipRect(mTmpDrawRect, Region.Op.REPLACE); + c.drawColor(color); + // Bottom + mTmpDrawRect.set(0, h - THICKNESS, w, h); + c.clipRect(mTmpDrawRect, Region.Op.REPLACE); + c.drawColor(color); + + mSurface.unlockCanvasAndPost(c); + } + + private void positionSurface(Rect bounds) { + if (false && DEBUG_STACK) Slog.i(TAG, "positionSurface: bounds=" + bounds.toShortString()); + mSurfaceControl.setSize(bounds.width(), bounds.height()); + mSurfaceControl.setPosition(bounds.left, bounds.top); + } + + // Note: caller responsible for being inside + // Surface.openTransaction() / closeTransaction() + public void setVisibility(boolean on) { + if (false && DEBUG_STACK) Slog.i(TAG, "setVisibility: on=" + on + + " mLastBounds=" + mLastBounds.toShortString() + + " mBounds=" + mBounds.toShortString()); + if (mSurfaceControl == null) { + return; + } + if (on) { + if (!mLastBounds.equals(mBounds)) { + // Erase the previous rectangle. + positionSurface(mLastBounds); + draw(mLastBounds, Color.TRANSPARENT); + // Draw the latest rectangle. + positionSurface(mBounds); + draw(mBounds, Color.WHITE); + // Update the history. + mLastBounds.set(mBounds); + } + mSurfaceControl.show(); + } else { + mSurfaceControl.hide(); + } + } + + public void setBounds(Rect bounds) { + if (false && DEBUG_STACK) Slog.i(TAG, "setBounds: bounds=" + bounds); + mBounds.set(bounds); + } + + public void setLayer(int layer) { + mSurfaceControl.setLayer(layer); + } +} diff --git a/services/java/com/android/server/wm/InputMonitor.java b/services/java/com/android/server/wm/InputMonitor.java index 0e360329da18..3d2ec4577acf 100644 --- a/services/java/com/android/server/wm/InputMonitor.java +++ b/services/java/com/android/server/wm/InputMonitor.java @@ -66,6 +66,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { * * Called by the InputManager. */ + @Override public void notifyInputChannelBroken(InputWindowHandle inputWindowHandle) { if (inputWindowHandle == null) { return; @@ -85,8 +86,9 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { * * Called by the InputManager. */ + @Override public long notifyANR(InputApplicationHandle inputApplicationHandle, - InputWindowHandle inputWindowHandle) { + InputWindowHandle inputWindowHandle, String reason) { AppWindowToken appWindowToken = null; WindowState windowState = null; boolean aboveSystem = false; @@ -103,7 +105,8 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { if (windowState != null) { Slog.i(WindowManagerService.TAG, "Input event dispatching timed out " - + "sending to " + windowState.mAttrs.getTitle()); + + "sending to " + windowState.mAttrs.getTitle() + + ". Reason: " + reason); // Figure out whether this window is layered above system windows. // We need to do this here to help the activity manager know how to // layer its ANR dialog. @@ -112,19 +115,21 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { aboveSystem = windowState.mBaseLayer > systemAlertLayer; } else if (appWindowToken != null) { Slog.i(WindowManagerService.TAG, "Input event dispatching timed out " - + "sending to application " + appWindowToken.stringName); + + "sending to application " + appWindowToken.stringName + + ". Reason: " + reason); } else { - Slog.i(WindowManagerService.TAG, "Input event dispatching timed out."); + Slog.i(WindowManagerService.TAG, "Input event dispatching timed out " + + ". Reason: " + reason); } - mService.saveANRStateLocked(appWindowToken, windowState); + mService.saveANRStateLocked(appWindowToken, windowState, reason); } if (appWindowToken != null && appWindowToken.appToken != null) { try { // Notify the activity manager about the timeout and let it decide whether // to abort dispatching or keep waiting. - boolean abort = appWindowToken.appToken.keyDispatchingTimedOut(); + boolean abort = appWindowToken.appToken.keyDispatchingTimedOut(reason); if (! abort) { // The activity manager declined to abort dispatching. // Wait a bit longer and timeout again later. @@ -137,7 +142,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { // Notify the activity manager about the timeout and let it decide whether // to abort dispatching or keep waiting. long timeout = ActivityManagerNative.getDefault().inputDispatchingTimedOut( - windowState.mSession.mPid, aboveSystem); + windowState.mSession.mPid, aboveSystem, reason); if (timeout >= 0) { // The activity manager declined to abort dispatching. // Wait a bit longer and timeout again later. @@ -161,11 +166,22 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { } private void addInputWindowHandleLw(final InputWindowHandle inputWindowHandle, - final WindowState child, final int flags, final int type, + final WindowState child, int flags, int privateFlags, final int type, final boolean isVisible, final boolean hasFocus, final boolean hasWallpaper) { // Add a window to our list of input windows. inputWindowHandle.name = child.toString(); + final boolean modal = (flags & (WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL + | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE)) == 0; + if (modal && child.mAppToken != null) { + // Limit the outer touch to the activity stack region. + flags |= WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL; + inputWindowHandle.touchableRegion.set(child.getStackBounds()); + } else { + // Not modal or full screen modal + child.getTouchableRegion(inputWindowHandle.touchableRegion); + } inputWindowHandle.layoutParamsFlags = flags; + inputWindowHandle.layoutParamsPrivateFlags = privateFlags; inputWindowHandle.layoutParamsType = type; inputWindowHandle.dispatchingTimeoutNanos = child.getInputDispatchingTimeoutNanos(); inputWindowHandle.visible = isVisible; @@ -193,7 +209,6 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { inputWindowHandle.scaleFactor = 1; } - child.getTouchableRegion(inputWindowHandle.touchableRegion); addInputWindowHandleLw(inputWindowHandle); } @@ -249,8 +264,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { // Add all windows on the default display. final int numDisplays = mService.mDisplayContents.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final WindowList windows = - mService.mDisplayContents.valueAt(displayNdx).getWindowList(); + WindowList windows = mService.mDisplayContents.valueAt(displayNdx).getWindowList(); for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) { final WindowState child = windows.get(winNdx); final InputChannel inputChannel = child.mInputChannel; @@ -261,6 +275,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { } final int flags = child.mAttrs.flags; + final int privateFlags = child.mAttrs.privateFlags; final int type = child.mAttrs.type; final boolean hasFocus = (child == mInputFocus); @@ -280,13 +295,14 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { final WindowState u = universeBackground.mWin; if (u.mInputChannel != null && u.mInputWindowHandle != null) { addInputWindowHandleLw(u.mInputWindowHandle, u, u.mAttrs.flags, - u.mAttrs.type, true, u == mInputFocus, false); + u.mAttrs.privateFlags, u.mAttrs.type, + true, u == mInputFocus, false); } addedUniverse = true; } if (child.mWinAnimator != universeBackground) { - addInputWindowHandleLw(inputWindowHandle, child, flags, type, + addInputWindowHandleLw(inputWindowHandle, child, flags, privateFlags, type, isVisible, hasFocus, hasWallpaper); } } @@ -302,6 +318,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { } /* Notifies that the input device configuration has changed. */ + @Override public void notifyConfigurationChanged() { mService.sendNewConfiguration(); @@ -327,12 +344,14 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { } /* Notifies that the lid switch changed state. */ + @Override public void notifyLidSwitchChanged(long whenNanos, boolean lidOpen) { mService.mPolicy.notifyLidSwitchChanged(whenNanos, lidOpen); } - + /* Provides an opportunity for the window manager policy to intercept early key * processing as soon as the key has been read from the device. */ + @Override public int interceptKeyBeforeQueueing( KeyEvent event, int policyFlags, boolean isScreenOn) { return mService.mPolicy.interceptKeyBeforeQueueing(event, policyFlags, isScreenOn); @@ -341,20 +360,23 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { /* Provides an opportunity for the window manager policy to intercept early * motion event processing when the screen is off since these events are normally * dropped. */ + @Override public int interceptMotionBeforeQueueingWhenScreenOff(int policyFlags) { return mService.mPolicy.interceptMotionBeforeQueueingWhenScreenOff(policyFlags); } /* Provides an opportunity for the window manager policy to process a key before * ordinary dispatch. */ + @Override public long interceptKeyBeforeDispatching( InputWindowHandle focus, KeyEvent event, int policyFlags) { WindowState windowState = focus != null ? (WindowState) focus.windowState : null; return mService.mPolicy.interceptKeyBeforeDispatching(windowState, event, policyFlags); } - + /* Provides an opportunity for the window manager policy to process a key that * the application did not handle. */ + @Override public KeyEvent dispatchUnhandledKey( InputWindowHandle focus, KeyEvent event, int policyFlags) { WindowState windowState = focus != null ? (WindowState) focus.windowState : null; @@ -362,6 +384,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { } /* Callback to get pointer layer. */ + @Override public int getPointerLayer() { return mService.mPolicy.windowTypeToLayerLw(WindowManager.LayoutParams.TYPE_POINTER) * WindowManagerService.TYPE_LAYER_MULTIPLIER @@ -372,7 +395,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { * Layer assignment is assumed to be complete by the time this is called. */ public void setInputFocusLw(WindowState newWindow, boolean updateInputWindows) { - if (WindowManagerService.DEBUG_INPUT) { + if (WindowManagerService.DEBUG_FOCUS_LIGHT || WindowManagerService.DEBUG_INPUT) { Slog.d(WindowManagerService.TAG, "Input focus has changed to " + newWindow); } @@ -392,7 +415,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { } } } - + public void setFocusedAppLw(AppWindowToken newApp) { // Focused app has changed. if (newApp == null) { @@ -405,7 +428,7 @@ final class InputMonitor implements InputManagerService.WindowManagerCallbacks { mService.mInputManager.setFocusedApplication(handle); } } - + public void pauseDispatchingLw(WindowToken window) { if (! window.paused) { if (WindowManagerService.DEBUG_INPUT) { diff --git a/services/java/com/android/server/wm/PointerEventDispatcher.java b/services/java/com/android/server/wm/PointerEventDispatcher.java new file mode 100644 index 000000000000..6b0e4c9f010a --- /dev/null +++ b/services/java/com/android/server/wm/PointerEventDispatcher.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.view.InputChannel; +import android.view.InputDevice; +import android.view.InputEvent; +import android.view.InputEventReceiver; +import android.view.MotionEvent; +import android.view.WindowManagerPolicy.PointerEventListener; + +import com.android.server.UiThread; + +import java.util.ArrayList; + +public class PointerEventDispatcher extends InputEventReceiver { + ArrayList<PointerEventListener> mListeners = new ArrayList<PointerEventListener>(); + PointerEventListener[] mListenersArray = new PointerEventListener[0]; + + public PointerEventDispatcher(InputChannel inputChannel) { + super(inputChannel, UiThread.getHandler().getLooper()); + } + + @Override + public void onInputEvent(InputEvent event) { + try { + if (event instanceof MotionEvent + && (event.getSource() & InputDevice.SOURCE_CLASS_POINTER) != 0) { + final MotionEvent motionEvent = (MotionEvent)event; + PointerEventListener[] listeners; + synchronized (mListeners) { + if (mListenersArray == null) { + mListenersArray = new PointerEventListener[mListeners.size()]; + mListeners.toArray(mListenersArray); + } + listeners = mListenersArray; + } + for (int i = 0; i < listeners.length; ++i) { + listeners[i].onPointerEvent(motionEvent); + } + } + } finally { + finishInputEvent(event, false); + } + } + + /** + * Add the specified listener to the list. + * @param listener The listener to add. + */ + public void registerInputEventListener(PointerEventListener listener) { + synchronized (mListeners) { + if (mListeners.contains(listener)) { + throw new IllegalStateException("registerInputEventListener: trying to register" + + listener + " twice."); + } + mListeners.add(listener); + mListenersArray = null; + } + } + + /** + * Remove the specified listener from the list. + * @param listener The listener to remove. + */ + public void unregisterInputEventListener(PointerEventListener listener) { + synchronized (mListeners) { + if (!mListeners.contains(listener)) { + throw new IllegalStateException("registerInputEventListener: " + listener + + " not registered."); + } + mListeners.remove(listener); + mListenersArray = null; + } + } +} diff --git a/services/java/com/android/server/wm/ScreenRotationAnimation.java b/services/java/com/android/server/wm/ScreenRotationAnimation.java index b2fbec14162a..e630737b40b7 100644 --- a/services/java/com/android/server/wm/ScreenRotationAnimation.java +++ b/services/java/com/android/server/wm/ScreenRotationAnimation.java @@ -26,6 +26,8 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.util.Slog; import android.view.Display; +import android.view.DisplayInfo; +import android.view.Surface.OutOfResourcesException; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -43,7 +45,7 @@ class ScreenRotationAnimation { static final int FREEZE_LAYER = WindowManagerService.TYPE_LAYER_MULTIPLIER * 200; final Context mContext; - final Display mDisplay; + final DisplayContent mDisplayContent; SurfaceControl mSurfaceControl; BlackFrame mCustomBlackFrame; BlackFrame mExitingBlackFrame; @@ -53,6 +55,8 @@ class ScreenRotationAnimation { int mOriginalRotation; int mOriginalWidth, mOriginalHeight; int mCurRotation; + Rect mOriginalDisplayRect = new Rect(); + Rect mCurrentDisplayRect = new Rect(); // For all animations, "exit" is for the UI elements that are going // away (that is the snapshot of the old screen), and "enter" is for @@ -108,6 +112,7 @@ class ScreenRotationAnimation { boolean mAnimRunning; boolean mFinishAnimReady; long mFinishAnimStartTime; + boolean mForceDefaultOrientation; final Matrix mFrameInitialMatrix = new Matrix(); final Matrix mSnapshotInitialMatrix = new Matrix(); @@ -186,14 +191,35 @@ class ScreenRotationAnimation { pw.print(prefix); pw.print("mExitFrameFinalMatrix="); mExitFrameFinalMatrix.printShortString(pw); pw.println(); + pw.print(prefix); pw.print("mForceDefaultOrientation="); pw.print(mForceDefaultOrientation); + if (mForceDefaultOrientation) { + pw.print(" mOriginalDisplayRect="); pw.print(mOriginalDisplayRect.toShortString()); + pw.print(" mCurrentDisplayRect="); pw.println(mCurrentDisplayRect.toShortString()); + } } - public ScreenRotationAnimation(Context context, Display display, SurfaceSession session, - boolean inTransaction, int originalWidth, int originalHeight, int originalRotation) { + public ScreenRotationAnimation(Context context, DisplayContent displayContent, + SurfaceSession session, boolean inTransaction, boolean forceDefaultOrientation) { mContext = context; - mDisplay = display; + mDisplayContent = displayContent; + displayContent.getLogicalDisplayRect(mOriginalDisplayRect); // Screenshot does NOT include rotation! + final Display display = displayContent.getDisplay(); + int originalRotation = display.getRotation(); + final int originalWidth; + final int originalHeight; + DisplayInfo displayInfo = displayContent.getDisplayInfo(); + if (forceDefaultOrientation) { + // Emulated orientation. + mForceDefaultOrientation = true; + originalWidth = displayContent.mBaseDisplayWidth; + originalHeight = displayContent.mBaseDisplayHeight; + } else { + // Normal situation + originalWidth = displayInfo.logicalWidth; + originalHeight = displayInfo.logicalHeight; + } if (originalRotation == Surface.ROTATION_90 || originalRotation == Surface.ROTATION_270) { mWidth = originalHeight; @@ -219,6 +245,8 @@ class ScreenRotationAnimation { mSurfaceControl = new SurfaceTrace(session, "ScreenshotSurface", mWidth, mHeight, PixelFormat.OPAQUE, SurfaceControl.HIDDEN); + Slog.w(TAG, "ScreenRotationAnimation ctor: displayOffset=" + + mOriginalDisplayRect.toShortString()); } else { mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface", mWidth, mHeight, @@ -230,12 +258,12 @@ class ScreenRotationAnimation { // FIXME: we should use the proper display SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay( SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur); - mSurfaceControl.setLayerStack(mDisplay.getLayerStack()); + mSurfaceControl.setLayerStack(display.getLayerStack()); mSurfaceControl.setLayer(FREEZE_LAYER + 1); mSurfaceControl.setAlpha(0); mSurfaceControl.show(); sur.destroy(); - } catch (SurfaceControl.OutOfResourcesException e) { + } catch (OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate freeze surface", e); } @@ -266,7 +294,14 @@ class ScreenRotationAnimation { private void setSnapshotTransformInTransaction(Matrix matrix, float alpha) { if (mSurfaceControl != null) { matrix.getValues(mTmpFloats); - mSurfaceControl.setPosition(mTmpFloats[Matrix.MTRANS_X], mTmpFloats[Matrix.MTRANS_Y]); + float x = mTmpFloats[Matrix.MTRANS_X]; + float y = mTmpFloats[Matrix.MTRANS_Y]; + if (mForceDefaultOrientation) { + mDisplayContent.getLogicalDisplayRect(mCurrentDisplayRect); + x -= mCurrentDisplayRect.left; + y -= mCurrentDisplayRect.top; + } + mSurfaceControl.setPosition(x, y); mSurfaceControl.setMatrix( mTmpFloats[Matrix.MSCALE_X], mTmpFloats[Matrix.MSKEW_Y], mTmpFloats[Matrix.MSKEW_X], mTmpFloats[Matrix.MSCALE_Y]); @@ -491,7 +526,7 @@ class ScreenRotationAnimation { mRotateFrameAnimation.scaleCurrentDuration(animationScale); } - final int layerStack = mDisplay.getLayerStack(); + final int layerStack = mDisplayContent.getDisplay().getLayerStack(); if (USE_CUSTOM_BLACK_FRAME && mCustomBlackFrame == null) { if (WindowManagerService.SHOW_LIGHT_TRANSACTIONS || DEBUG_STATE) Slog.i( WindowManagerService.TAG, @@ -511,9 +546,9 @@ class ScreenRotationAnimation { mOriginalWidth*2, mOriginalHeight*2); Rect inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight); mCustomBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER + 3, - layerStack); + layerStack, false); mCustomBlackFrame.setMatrix(mFrameInitialMatrix); - } catch (SurfaceControl.OutOfResourcesException e) { + } catch (OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate black surface", e); } finally { SurfaceControl.closeTransaction(); @@ -537,13 +572,23 @@ class ScreenRotationAnimation { // we were last in. createRotationMatrix(delta, mOriginalWidth, mOriginalHeight, mFrameInitialMatrix); - Rect outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1, - mOriginalWidth*2, mOriginalHeight*2); - Rect inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight); + final Rect outer; + final Rect inner; + if (mForceDefaultOrientation) { + // Going from a smaller Display to a larger Display, add curtains to sides + // or top and bottom. Going from a larger to smaller display will result in + // no BlackSurfaces being constructed. + outer = mCurrentDisplayRect; + inner = mOriginalDisplayRect; + } else { + outer = new Rect(-mOriginalWidth*1, -mOriginalHeight*1, + mOriginalWidth*2, mOriginalHeight*2); + inner = new Rect(0, 0, mOriginalWidth, mOriginalHeight); + } mExitingBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER + 2, - layerStack); + layerStack, mForceDefaultOrientation); mExitingBlackFrame.setMatrix(mFrameInitialMatrix); - } catch (SurfaceControl.OutOfResourcesException e) { + } catch (OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate black surface", e); } finally { SurfaceControl.closeTransaction(); @@ -564,8 +609,8 @@ class ScreenRotationAnimation { finalWidth*2, finalHeight*2); Rect inner = new Rect(0, 0, finalWidth, finalHeight); mEnteringBlackFrame = new BlackFrame(session, outer, inner, FREEZE_LAYER, - layerStack); - } catch (SurfaceControl.OutOfResourcesException e) { + layerStack, false); + } catch (OutOfResourcesException e) { Slog.w(TAG, "Unable to allocate black surface", e); } finally { SurfaceControl.closeTransaction(); @@ -850,7 +895,7 @@ class ScreenRotationAnimation { && (mMoreStartEnter || mMoreStartExit || mMoreFinishEnter || mMoreFinishExit)) || (USE_CUSTOM_BLACK_FRAME && (mMoreStartFrame || mMoreRotateFrame || mMoreFinishFrame)) - || mMoreRotateEnter || mMoreRotateExit + || mMoreRotateEnter || mMoreRotateExit || !mFinishAnimReady; mSnapshotFinalMatrix.setConcat(mExitTransformation.getMatrix(), mSnapshotInitialMatrix); @@ -888,6 +933,9 @@ class ScreenRotationAnimation { } else { mExitFrameFinalMatrix.setConcat(mExitTransformation.getMatrix(), mFrameInitialMatrix); mExitingBlackFrame.setMatrix(mExitFrameFinalMatrix); + if (mForceDefaultOrientation) { + mExitingBlackFrame.setAlpha(mExitTransformation.getAlpha()); + } } } diff --git a/services/java/com/android/server/wm/Session.java b/services/java/com/android/server/wm/Session.java index 1d95c44c8b15..87cabc9a510b 100644 --- a/services/java/com/android/server/wm/Session.java +++ b/services/java/com/android/server/wm/Session.java @@ -126,7 +126,7 @@ final class Session extends IWindowSession.Stub } catch (RuntimeException e) { // Log all 'real' exceptions thrown to the caller if (!(e instanceof SecurityException)) { - Slog.e(WindowManagerService.TAG, "Window Session Crash", e); + Slog.wtf(WindowManagerService.TAG, "Window Session Crash", e); } throw e; } diff --git a/services/java/com/android/server/wm/StackBox.java b/services/java/com/android/server/wm/StackBox.java new file mode 100644 index 000000000000..d054e9a25ee1 --- /dev/null +++ b/services/java/com/android/server/wm/StackBox.java @@ -0,0 +1,418 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.graphics.Rect; +import android.util.Slog; + +import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; +import static com.android.server.wm.WindowManagerService.DEBUG_STACK; +import static com.android.server.wm.WindowManagerService.TAG; + +import java.io.PrintWriter; + +public class StackBox { + /** Used with {@link WindowManagerService#createStack}. Dependent on Configuration LTR/RTL. */ + public static final int TASK_STACK_GOES_BEFORE = 0; + /** Used with {@link WindowManagerService#createStack}. Dependent on Configuration LTR/RTL. */ + public static final int TASK_STACK_GOES_AFTER = 1; + /** Used with {@link WindowManagerService#createStack}. Horizontal to left of. */ + public static final int TASK_STACK_TO_LEFT_OF = 2; + /** Used with {@link WindowManagerService#createStack}. Horizontal to right of. */ + public static final int TASK_STACK_TO_RIGHT_OF = 3; + /** Used with {@link WindowManagerService#createStack}. Vertical: lower t/b Rect values. */ + public static final int TASK_STACK_GOES_ABOVE = 4; + /** Used with {@link WindowManagerService#createStack}. Vertical: higher t/b Rect values. */ + public static final int TASK_STACK_GOES_BELOW = 5; + /** Used with {@link WindowManagerService#createStack}. Put on a higher layer on display. */ + public static final int TASK_STACK_GOES_OVER = 6; + /** Used with {@link WindowManagerService#createStack}. Put on a lower layer on display. */ + public static final int TASK_STACK_GOES_UNDER = 7; + + static int sCurrentBoxId = 0; + + /** Unique id for this box */ + final int mStackBoxId; + + /** The service */ + final WindowManagerService mService; + + /** The display this box sits in. */ + final DisplayContent mDisplayContent; + + /** Non-null indicates this is mFirst or mSecond of a parent StackBox. Null indicates this + * is this entire size of mDisplayContent. */ + StackBox mParent; + + /** First child, this is null exactly when mStack is non-null. */ + StackBox mFirst; + + /** Second child, this is null exactly when mStack is non-null. */ + StackBox mSecond; + + /** Stack of Tasks, this is null exactly when mFirst and mSecond are non-null. */ + TaskStack mStack; + + /** Content limits relative to the DisplayContent this sits in. */ + Rect mBounds = new Rect(); + + /** Relative orientation of mFirst and mSecond. */ + boolean mVertical; + + /** Fraction of mBounds to devote to mFirst, remainder goes to mSecond */ + float mWeight; + + /** Dirty flag. Something inside this or some descendant of this has changed. */ + boolean layoutNeeded; + + /** True if this StackBox sits below the Status Bar. */ + boolean mUnderStatusBar; + + /** Used to keep from reallocating a temporary Rect for propagating bounds to child boxes */ + Rect mTmpRect = new Rect(); + + StackBox(WindowManagerService service, DisplayContent displayContent, StackBox parent) { + synchronized (StackBox.class) { + mStackBoxId = sCurrentBoxId++; + } + + mService = service; + mDisplayContent = displayContent; + mParent = parent; + } + + /** Propagate #layoutNeeded bottom up. */ + void makeDirty() { + layoutNeeded = true; + if (mParent != null) { + mParent.makeDirty(); + } + } + + /** + * Determine if a particular StackBox is this one or a descendant of this one. + * @param stackBoxId The StackBox being searched for. + * @return true if the specified StackBox matches this or one of its descendants. + */ + boolean contains(int stackBoxId) { + return mStackBoxId == stackBoxId || + (mStack == null && (mFirst.contains(stackBoxId) || mSecond.contains(stackBoxId))); + } + + /** + * Return the stackId of the stack that intersects the passed point. + * @param x coordinate of point. + * @param y coordinate of point. + * @return -1 if point is outside of mBounds, otherwise the stackId of the containing stack. + */ + int stackIdFromPoint(int x, int y) { + if (!mBounds.contains(x, y)) { + return -1; + } + if (mStack != null) { + return mStack.mStackId; + } + int stackId = mFirst.stackIdFromPoint(x, y); + if (stackId >= 0) { + return stackId; + } + return mSecond.stackIdFromPoint(x, y); + } + + /** Determine if this StackBox is the first child or second child. + * @return true if this is the first child. + */ + boolean isFirstChild() { + return mParent != null && mParent.mFirst == this; + } + + /** Returns the bounds of the specified TaskStack if it is contained in this StackBox. + * @param stackId the TaskStack to find the bounds of. + * @return a new Rect with the bounds of stackId if it is within this StackBox, null otherwise. + */ + Rect getStackBounds(int stackId) { + if (mStack != null) { + return mStack.mStackId == stackId ? new Rect(mBounds) : null; + } + Rect bounds = mFirst.getStackBounds(stackId); + if (bounds != null) { + return bounds; + } + return mSecond.getStackBounds(stackId); + } + + /** + * Create a new TaskStack relative to a specified one by splitting the StackBox containing + * the specified TaskStack into two children. The size and position each of the new StackBoxes + * is determined by the passed parameters. + * @param stackId The id of the new TaskStack to create. + * @param relativeStackBoxId The id of the StackBox to place the new TaskStack next to. + * @param position One of the static TASK_STACK_GOES_xxx positions defined in this class. + * @param weight The percentage size of the parent StackBox to devote to the new TaskStack. + * @return The new TaskStack. + */ + TaskStack split(int stackId, int relativeStackBoxId, int position, float weight) { + if (mStackBoxId != relativeStackBoxId) { + // This is not the targeted StackBox. + if (mStack != null) { + return null; + } + // Propagate the split to see if the targeted StackBox is in either sub box. + TaskStack stack = mFirst.split(stackId, relativeStackBoxId, position, weight); + if (stack != null) { + return stack; + } + return mSecond.split(stackId, relativeStackBoxId, position, weight); + } + + // Found it! + TaskStack stack = new TaskStack(mService, stackId, mDisplayContent); + TaskStack firstStack; + TaskStack secondStack; + if (position == TASK_STACK_GOES_BEFORE) { + // TODO: Test Configuration here for LTR/RTL. + position = TASK_STACK_TO_LEFT_OF; + } else if (position == TASK_STACK_GOES_AFTER) { + // TODO: Test Configuration here for LTR/RTL. + position = TASK_STACK_TO_RIGHT_OF; + } + switch (position) { + default: + case TASK_STACK_TO_LEFT_OF: + case TASK_STACK_TO_RIGHT_OF: + mVertical = false; + if (position == TASK_STACK_TO_LEFT_OF) { + mWeight = weight; + firstStack = stack; + secondStack = mStack; + } else { + mWeight = 1.0f - weight; + firstStack = mStack; + secondStack = stack; + } + break; + case TASK_STACK_GOES_ABOVE: + case TASK_STACK_GOES_BELOW: + mVertical = true; + if (position == TASK_STACK_GOES_ABOVE) { + mWeight = weight; + firstStack = stack; + secondStack = mStack; + } else { + mWeight = 1.0f - weight; + firstStack = mStack; + secondStack = stack; + } + break; + } + + mFirst = new StackBox(mService, mDisplayContent, this); + firstStack.mStackBox = mFirst; + mFirst.mStack = firstStack; + + mSecond = new StackBox(mService, mDisplayContent, this); + secondStack.mStackBox = mSecond; + mSecond.mStack = secondStack; + + mStack = null; + return stack; + } + + /** Return the stackId of the first mFirst StackBox with a non-null mStack */ + int getStackId() { + if (mStack != null) { + return mStack.mStackId; + } + return mFirst.getStackId(); + } + + /** Remove this box and propagate its sibling's content up to their parent. + * @return The first stackId of the resulting StackBox. */ + int remove() { + if (mStack != null) { + if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: removing stackId=" + mStack.mStackId); + mDisplayContent.mStackHistory.remove(mStack); + } + mDisplayContent.layoutNeeded = true; + + if (mParent == null) { + // This is the top-plane stack. + if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: removing top plane."); + mDisplayContent.removeStackBox(this); + return HOME_STACK_ID; + } + + StackBox sibling = isFirstChild() ? mParent.mSecond : mParent.mFirst; + StackBox grandparent = mParent.mParent; + sibling.mParent = grandparent; + if (grandparent == null) { + // mParent is a top-plane stack. Now sibling will be. + if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: grandparent null"); + mDisplayContent.removeStackBox(mParent); + mDisplayContent.addStackBox(sibling, true); + } else { + if (DEBUG_STACK) Slog.i(TAG, "StackBox.remove: grandparent getting sibling"); + if (mParent.isFirstChild()) { + grandparent.mFirst = sibling; + } else { + grandparent.mSecond = sibling; + } + } + return sibling.getStackId(); + } + + boolean resize(int stackBoxId, float weight) { + if (mStackBoxId != stackBoxId) { + return mStack == null && + (mFirst.resize(stackBoxId, weight) || mSecond.resize(stackBoxId, weight)); + } + // Don't change weight on topmost stack. + if (mParent != null) { + mParent.mWeight = isFirstChild() ? weight : 1.0f - weight; + } + return true; + } + + /** If this is a terminal StackBox (contains a TaskStack) set the bounds. + * @param bounds The rectangle to set the bounds to. + * @param underStatusBar True if the StackBox is directly below the Status Bar. + * @return True if the bounds changed, false otherwise. */ + boolean setStackBoxSizes(Rect bounds, boolean underStatusBar) { + boolean change = false; + if (mUnderStatusBar != underStatusBar) { + change = true; + mUnderStatusBar = underStatusBar; + } + if (mStack != null) { + change |= !mBounds.equals(bounds); + if (change) { + mBounds.set(bounds); + mStack.setBounds(bounds, underStatusBar); + } + } else { + mTmpRect.set(bounds); + if (mVertical) { + final int height = bounds.height(); + int firstHeight = (int)(height * mWeight); + mTmpRect.bottom = bounds.top + firstHeight; + change |= mFirst.setStackBoxSizes(mTmpRect, underStatusBar); + mTmpRect.top = mTmpRect.bottom; + mTmpRect.bottom = bounds.top + height; + change |= mSecond.setStackBoxSizes(mTmpRect, false); + } else { + final int width = bounds.width(); + int firstWidth = (int)(width * mWeight); + mTmpRect.right = bounds.left + firstWidth; + change |= mFirst.setStackBoxSizes(mTmpRect, underStatusBar); + mTmpRect.left = mTmpRect.right; + mTmpRect.right = bounds.left + width; + change |= mSecond.setStackBoxSizes(mTmpRect, underStatusBar); + } + } + return change; + } + + void resetAnimationBackgroundAnimator() { + if (mStack != null) { + mStack.resetAnimationBackgroundAnimator(); + return; + } + mFirst.resetAnimationBackgroundAnimator(); + mSecond.resetAnimationBackgroundAnimator(); + } + + boolean animateDimLayers() { + if (mStack != null) { + return mStack.animateDimLayers(); + } + boolean result = mFirst.animateDimLayers(); + result |= mSecond.animateDimLayers(); + return result; + } + + void resetDimming() { + if (mStack != null) { + mStack.resetDimmingTag(); + return; + } + mFirst.resetDimming(); + mSecond.resetDimming(); + } + + boolean isDimming() { + if (mStack != null) { + return mStack.isDimming(); + } + boolean result = mFirst.isDimming(); + result |= mSecond.isDimming(); + return result; + } + + void stopDimmingIfNeeded() { + if (mStack != null) { + mStack.stopDimmingIfNeeded(); + return; + } + mFirst.stopDimmingIfNeeded(); + mSecond.stopDimmingIfNeeded(); + } + + void switchUserStacks(int userId) { + if (mStack != null) { + mStack.switchUser(userId); + return; + } + mFirst.switchUserStacks(userId); + mSecond.switchUserStacks(userId); + } + + void close() { + if (mStack != null) { + mStack.mDimLayer.mDimSurface.destroy(); + mStack.mAnimationBackgroundSurface.mDimSurface.destroy(); + return; + } + mFirst.close(); + mSecond.close(); + } + + public void dump(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("mParent="); pw.println(mParent); + pw.print(prefix); pw.print("mBounds="); pw.print(mBounds.toShortString()); + pw.print(" mVertical="); pw.print(mVertical); + pw.print(" layoutNeeded="); pw.println(layoutNeeded); + if (mFirst != null) { + pw.print(prefix); pw.print("mFirst="); pw.println(System.identityHashCode(mFirst)); + mFirst.dump(prefix + " ", pw); + pw.print(prefix); pw.print("mSecond="); pw.println(System.identityHashCode(mSecond)); + mSecond.dump(prefix + " ", pw); + } else { + pw.print(prefix); pw.print("mStack="); pw.println(mStack); + mStack.dump(prefix + " ", pw); + } + } + + @Override + public String toString() { + if (mStack != null) { + return "Box{" + hashCode() + " stack=" + mStack.mStackId + "}"; + } + return "Box{" + hashCode() + " parent=" + System.identityHashCode(mParent) + + " first=" + System.identityHashCode(mFirst) + + " second=" + System.identityHashCode(mSecond) + "}"; + } +} diff --git a/services/java/com/android/server/wm/StackTapPointerEventListener.java b/services/java/com/android/server/wm/StackTapPointerEventListener.java new file mode 100644 index 000000000000..19d8ab372afe --- /dev/null +++ b/services/java/com/android/server/wm/StackTapPointerEventListener.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.graphics.Region; +import android.view.DisplayInfo; +import android.view.MotionEvent; +import android.view.WindowManagerPolicy.PointerEventListener; + +import com.android.server.wm.WindowManagerService.H; + +public class StackTapPointerEventListener implements PointerEventListener { + private static final int TAP_TIMEOUT_MSEC = 300; + private static final float TAP_MOTION_SLOP_INCHES = 0.125f; + + private final int mMotionSlop; + private float mDownX; + private float mDownY; + private int mPointerId; + final private Region mTouchExcludeRegion; + private final WindowManagerService mService; + private final DisplayContent mDisplayContent; + + public StackTapPointerEventListener(WindowManagerService service, + DisplayContent displayContent) { + mService = service; + mDisplayContent = displayContent; + mTouchExcludeRegion = displayContent.mTouchExcludeRegion; + DisplayInfo info = displayContent.getDisplayInfo(); + mMotionSlop = (int)(info.logicalDensityDpi * TAP_MOTION_SLOP_INCHES); + } + + @Override + public void onPointerEvent(MotionEvent motionEvent) { + final int action = motionEvent.getAction(); + switch (action & MotionEvent.ACTION_MASK) { + case MotionEvent.ACTION_DOWN: + mPointerId = motionEvent.getPointerId(0); + mDownX = motionEvent.getX(); + mDownY = motionEvent.getY(); + break; + case MotionEvent.ACTION_MOVE: + if (mPointerId >= 0) { + int index = motionEvent.findPointerIndex(mPointerId); + if ((motionEvent.getEventTime() - motionEvent.getDownTime()) > TAP_TIMEOUT_MSEC + || (motionEvent.getX(index) - mDownX) > mMotionSlop + || (motionEvent.getY(index) - mDownY) > mMotionSlop) { + mPointerId = -1; + } + } + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: { + int index = (action & MotionEvent.ACTION_POINTER_INDEX_MASK) + >> MotionEvent.ACTION_POINTER_INDEX_SHIFT; + // Extract the index of the pointer that left the touch sensor + if (mPointerId == motionEvent.getPointerId(index)) { + final int x = (int)motionEvent.getX(index); + final int y = (int)motionEvent.getY(index); + if ((motionEvent.getEventTime() - motionEvent.getDownTime()) + < TAP_TIMEOUT_MSEC + && (x - mDownX) < mMotionSlop && (y - mDownY) < mMotionSlop + && !mTouchExcludeRegion.contains(x, y)) { + mService.mH.obtainMessage(H.TAP_OUTSIDE_STACK, x, y, + mDisplayContent).sendToTarget(); + } + mPointerId = -1; + } + break; + } + } + } +} diff --git a/services/java/com/android/server/wm/StartingData.java b/services/java/com/android/server/wm/StartingData.java index 46bb480a08dd..7115b0fd02e5 100644 --- a/services/java/com/android/server/wm/StartingData.java +++ b/services/java/com/android/server/wm/StartingData.java @@ -25,17 +25,19 @@ final class StartingData { final CharSequence nonLocalizedLabel; final int labelRes; final int icon; + final int logo; final int windowFlags; StartingData(String _pkg, int _theme, CompatibilityInfo _compatInfo, CharSequence _nonLocalizedLabel, - int _labelRes, int _icon, int _windowFlags) { + int _labelRes, int _icon, int _logo, int _windowFlags) { pkg = _pkg; theme = _theme; compatInfo = _compatInfo; nonLocalizedLabel = _nonLocalizedLabel; labelRes = _labelRes; icon = _icon; + logo = _logo; windowFlags = _windowFlags; } }
\ No newline at end of file diff --git a/services/java/com/android/server/wm/StrictModeFlash.java b/services/java/com/android/server/wm/StrictModeFlash.java index 31628e33866a..fb5876b65aed 100644 --- a/services/java/com/android/server/wm/StrictModeFlash.java +++ b/services/java/com/android/server/wm/StrictModeFlash.java @@ -23,6 +23,7 @@ import android.graphics.PixelFormat; import android.graphics.Rect; import android.graphics.Region; import android.view.Display; +import android.view.Surface.OutOfResourcesException; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -47,7 +48,7 @@ class StrictModeFlash { ctrl.setPosition(0, 0); ctrl.show(); mSurface.copyFrom(ctrl); - } catch (SurfaceControl.OutOfResourcesException e) { + } catch (OutOfResourcesException e) { } mSurfaceControl = ctrl; mDrawNeeded = true; diff --git a/services/java/com/android/server/wm/Task.java b/services/java/com/android/server/wm/Task.java new file mode 100644 index 000000000000..d9acbb95f72f --- /dev/null +++ b/services/java/com/android/server/wm/Task.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +class Task { +// private final String TAG = "TaskGroup"; + TaskStack mStack; + final AppTokenList mAppTokens = new AppTokenList(); + final int taskId; + final int mUserId; + + Task(AppWindowToken wtoken, TaskStack stack, int userId) { + taskId = wtoken.groupId; + mAppTokens.add(wtoken); + mStack = stack; + mUserId = userId; + } + + DisplayContent getDisplayContent() { + return mStack.getDisplayContent(); + } + + void addAppToken(int addPos, AppWindowToken wtoken) { + mAppTokens.add(addPos, wtoken); + } + + boolean removeAppToken(AppWindowToken wtoken) { + mAppTokens.remove(wtoken); + if (mAppTokens.size() == 0) { + mStack.removeTask(this); + return true; + } + return false; + } + + @Override + public String toString() { + return "{taskId=" + taskId + " appTokens=" + mAppTokens + "}"; + } +} diff --git a/services/java/com/android/server/wm/TaskGroup.java b/services/java/com/android/server/wm/TaskGroup.java new file mode 100644 index 000000000000..1f1dd5832d55 --- /dev/null +++ b/services/java/com/android/server/wm/TaskGroup.java @@ -0,0 +1,31 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import android.view.IApplicationToken; + +import java.util.ArrayList; + +public class TaskGroup { + public int taskId = -1; + public ArrayList<IApplicationToken> tokens = new ArrayList<IApplicationToken>(); + + @Override + public String toString() { + return "id=" + taskId + " tokens=" + tokens; + } +} diff --git a/services/java/com/android/server/wm/TaskStack.java b/services/java/com/android/server/wm/TaskStack.java new file mode 100644 index 000000000000..2347a19f4094 --- /dev/null +++ b/services/java/com/android/server/wm/TaskStack.java @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2013 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.wm; + +import static com.android.server.wm.WindowManagerService.DEBUG_TASK_MOVEMENT; +import static com.android.server.wm.WindowManagerService.TAG; + +import android.graphics.Rect; +import android.os.Debug; +import android.util.Slog; +import android.util.TypedValue; + +import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; + +import java.io.PrintWriter; +import java.util.ArrayList; + +public class TaskStack { + /** Amount of time in milliseconds to animate the dim surface from one value to another, + * when no window animation is driving it. */ + private static final int DEFAULT_DIM_DURATION = 200; + + /** Unique identifier */ + final int mStackId; + + /** The service */ + private final WindowManagerService mService; + + /** The display this stack sits under. */ + private final DisplayContent mDisplayContent; + + /** The Tasks that define this stack. Oldest Tasks are at the bottom. The ordering must match + * mTaskHistory in the ActivityStack with the same mStackId */ + private ArrayList<Task> mTasks = new ArrayList<Task>(); + + /** The StackBox this sits in. */ + StackBox mStackBox; + + /** Used to support {@link android.view.WindowManager.LayoutParams#FLAG_DIM_BEHIND} */ + final DimLayer mDimLayer; + + /** The particular window with FLAG_DIM_BEHIND set. If null, hide mDimLayer. */ + WindowStateAnimator mDimWinAnimator; + + /** Support for non-zero {@link android.view.animation.Animation#getBackgroundColor()} */ + final DimLayer mAnimationBackgroundSurface; + + /** The particular window with an Animation with non-zero background color. */ + WindowStateAnimator mAnimationBackgroundAnimator; + + /** Set to false at the start of performLayoutAndPlaceSurfaces. If it is still false by the end + * then stop any dimming. */ + boolean mDimmingTag; + + TaskStack(WindowManagerService service, int stackId, DisplayContent displayContent) { + mService = service; + mStackId = stackId; + mDisplayContent = displayContent; + final int displayId = displayContent.getDisplayId(); + mDimLayer = new DimLayer(service, this); + mAnimationBackgroundSurface = new DimLayer(service, this); + } + + DisplayContent getDisplayContent() { + return mDisplayContent; + } + + ArrayList<Task> getTasks() { + return mTasks; + } + + boolean isHomeStack() { + return mStackId == HOME_STACK_ID; + } + + boolean hasSibling() { + return mStackBox.mParent != null; + } + + /** + * Put a Task in this stack. Used for adding and moving. + * @param task The task to add. + * @param toTop Whether to add it to the top or bottom. + */ + boolean addTask(Task task, boolean toTop) { + mStackBox.makeDirty(); + + int stackNdx; + if (!toTop) { + stackNdx = 0; + } else { + stackNdx = mTasks.size(); + final int currentUserId = mService.mCurrentUserId; + if (task.mUserId != currentUserId) { + // Place the task below all current user tasks. + while (--stackNdx >= 0) { + if (currentUserId != mTasks.get(stackNdx).mUserId) { + break; + } + } + ++stackNdx; + } + } + if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "addTask: task=" + task + " toTop=" + toTop + + " pos=" + stackNdx); + mTasks.add(stackNdx, task); + + task.mStack = this; + return mDisplayContent.moveHomeStackBox(mStackId == HOME_STACK_ID); + } + + boolean moveTaskToTop(Task task) { + if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "moveTaskToTop: task=" + task + " Callers=" + + Debug.getCallers(6)); + mTasks.remove(task); + return addTask(task, true); + } + + boolean moveTaskToBottom(Task task) { + if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "moveTaskToBottom: task=" + task); + mTasks.remove(task); + return addTask(task, false); + } + + /** + * Delete a Task from this stack. If it is the last Task in the stack, remove this stack from + * its parent StackBox and merge the parent. + * @param task The Task to delete. + */ + void removeTask(Task task) { + if (DEBUG_TASK_MOVEMENT) Slog.d(TAG, "removeTask: task=" + task); + mStackBox.makeDirty(); + mTasks.remove(task); + } + + int remove() { + mAnimationBackgroundSurface.destroySurface(); + mDimLayer.destroySurface(); + return mStackBox.remove(); + } + + void resetAnimationBackgroundAnimator() { + mAnimationBackgroundAnimator = null; + mAnimationBackgroundSurface.hide(); + } + + private long getDimBehindFadeDuration(long duration) { + TypedValue tv = new TypedValue(); + mService.mContext.getResources().getValue( + com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true); + if (tv.type == TypedValue.TYPE_FRACTION) { + duration = (long)tv.getFraction(duration, duration); + } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) { + duration = tv.data; + } + return duration; + } + + boolean animateDimLayers() { + final int dimLayer; + final float dimAmount; + if (mDimWinAnimator == null) { + dimLayer = mDimLayer.getLayer(); + dimAmount = 0; + } else { + dimLayer = mDimWinAnimator.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM; + dimAmount = mDimWinAnimator.mWin.mAttrs.dimAmount; + } + final float targetAlpha = mDimLayer.getTargetAlpha(); + if (targetAlpha != dimAmount) { + if (mDimWinAnimator == null) { + mDimLayer.hide(DEFAULT_DIM_DURATION); + } else { + long duration = (mDimWinAnimator.mAnimating && mDimWinAnimator.mAnimation != null) + ? mDimWinAnimator.mAnimation.computeDurationHint() + : DEFAULT_DIM_DURATION; + if (targetAlpha > dimAmount) { + duration = getDimBehindFadeDuration(duration); + } + mDimLayer.show(dimLayer, dimAmount, duration); + } + } else if (mDimLayer.getLayer() != dimLayer) { + mDimLayer.setLayer(dimLayer); + } + if (mDimLayer.isAnimating()) { + if (!mService.okToDisplay()) { + // Jump to the end of the animation. + mDimLayer.show(); + } else { + return mDimLayer.stepAnimation(); + } + } + return false; + } + + void resetDimmingTag() { + mDimmingTag = false; + } + + void setDimmingTag() { + mDimmingTag = true; + } + + boolean testDimmingTag() { + return mDimmingTag; + } + + boolean isDimming() { + return mDimLayer.isDimming(); + } + + boolean isDimming(WindowStateAnimator winAnimator) { + return mDimWinAnimator == winAnimator && mDimLayer.isDimming(); + } + + void startDimmingIfNeeded(WindowStateAnimator newWinAnimator) { + // Only set dim params on the highest dimmed layer. + final WindowStateAnimator existingDimWinAnimator = mDimWinAnimator; + // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer. + if (newWinAnimator.mSurfaceShown && (existingDimWinAnimator == null + || !existingDimWinAnimator.mSurfaceShown + || existingDimWinAnimator.mAnimLayer < newWinAnimator.mAnimLayer)) { + mDimWinAnimator = newWinAnimator; + } + } + + void stopDimmingIfNeeded() { + if (!mDimmingTag && isDimming()) { + mDimWinAnimator = null; + } + } + + void setAnimationBackground(WindowStateAnimator winAnimator, int color) { + int animLayer = winAnimator.mAnimLayer; + if (mAnimationBackgroundAnimator == null + || animLayer < mAnimationBackgroundAnimator.mAnimLayer) { + mAnimationBackgroundAnimator = winAnimator; + animLayer = mService.adjustAnimationBackground(winAnimator); + mAnimationBackgroundSurface.show(animLayer - WindowManagerService.LAYER_OFFSET_DIM, + ((color >> 24) & 0xff) / 255f, 0); + } + } + + void setBounds(Rect bounds, boolean underStatusBar) { + mDimLayer.setBounds(bounds); + mAnimationBackgroundSurface.setBounds(bounds); + + final ArrayList<WindowState> resizingWindows = mService.mResizingWindows; + for (int taskNdx = mTasks.size() - 1; taskNdx >= 0; --taskNdx) { + final ArrayList<AppWindowToken> activities = mTasks.get(taskNdx).mAppTokens; + for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) { + final ArrayList<WindowState> windows = activities.get(activityNdx).allAppWindows; + for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) { + final WindowState win = windows.get(winNdx); + if (!resizingWindows.contains(win)) { + resizingWindows.add(win); + } + win.mUnderStatusBar = underStatusBar; + } + } + } + } + + void switchUser(int userId) { + int top = mTasks.size(); + for (int taskNdx = 0; taskNdx < top; ++taskNdx) { + Task task = mTasks.get(taskNdx); + if (task.mUserId == userId) { + mTasks.remove(taskNdx); + mTasks.add(task); + --top; + } + } + } + + public void dump(String prefix, PrintWriter pw) { + pw.print(prefix); pw.print("mStackId="); pw.println(mStackId); + for (int taskNdx = 0; taskNdx < mTasks.size(); ++taskNdx) { + pw.print(prefix); pw.println(mTasks.get(taskNdx)); + } + if (mAnimationBackgroundSurface.isDimming()) { + pw.print(prefix); pw.println("mWindowAnimationBackgroundSurface:"); + mAnimationBackgroundSurface.printTo(prefix + " ", pw); + } + if (mDimLayer.isDimming()) { + pw.print(prefix); pw.println("mDimLayer:"); + mDimLayer.printTo(prefix, pw); + pw.print(prefix); pw.print("mDimWinAnimator="); pw.println(mDimWinAnimator); + } + } + + @Override + public String toString() { + return "{stackId=" + mStackId + " tasks=" + mTasks + "}"; + } +} diff --git a/services/java/com/android/server/wm/Watermark.java b/services/java/com/android/server/wm/Watermark.java index fedd31416f03..e226e3d73e47 100644 --- a/services/java/com/android/server/wm/Watermark.java +++ b/services/java/com/android/server/wm/Watermark.java @@ -27,10 +27,10 @@ import android.util.DisplayMetrics; import android.util.Log; import android.util.TypedValue; import android.view.Display; +import android.view.Surface.OutOfResourcesException; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; -import android.view.Surface.OutOfResourcesException; /** * Displays a watermark on top of the window manager's windows. @@ -119,7 +119,7 @@ class Watermark { ctrl.setPosition(0, 0); ctrl.show(); mSurface.copyFrom(ctrl); - } catch (SurfaceControl.OutOfResourcesException e) { + } catch (OutOfResourcesException e) { } mSurfaceControl = ctrl; } @@ -144,11 +144,11 @@ class Watermark { try { c = mSurface.lockCanvas(dirty); } catch (IllegalArgumentException e) { - } catch (OutOfResourcesException e) { + } catch (Surface.OutOfResourcesException e) { } if (c != null) { c.drawColor(0, PorterDuff.Mode.CLEAR); - + int deltaX = mDeltaX; int deltaY = mDeltaY; diff --git a/services/java/com/android/server/wm/WindowAnimator.java b/services/java/com/android/server/wm/WindowAnimator.java index 425285757ff6..ca87b4ffa440 100644 --- a/services/java/com/android/server/wm/WindowAnimator.java +++ b/services/java/com/android/server/wm/WindowAnimator.java @@ -19,9 +19,7 @@ import android.util.Slog; import android.util.SparseArray; import android.util.SparseIntArray; import android.util.TimeUtils; -import android.util.TypedValue; import android.view.Display; -import android.view.Surface; import android.view.SurfaceControl; import android.view.WindowManagerPolicy; import android.view.animation.Animation; @@ -38,10 +36,6 @@ import java.util.ArrayList; public class WindowAnimator { private static final String TAG = "WindowAnimator"; - /** Amount of time in milliseconds to animate the dim surface from one value to another, - * when no window animation is driving it. */ - static final int DEFAULT_DIM_DURATION = 200; - final WindowManagerService mService; final Context mContext; final WindowManagerPolicy mPolicy; @@ -50,8 +44,6 @@ public class WindowAnimator { final Runnable mAnimationRunnable; - int mAdjResult; - /** Time of current animation step. Reset on each iteration */ long mCurrentTime; @@ -72,7 +64,7 @@ public class WindowAnimator { Object mLastWindowFreezeSource; SparseArray<DisplayContentsAnimator> mDisplayContentsAnimators = - new SparseArray<WindowAnimator.DisplayContentsAnimator>(); + new SparseArray<WindowAnimator.DisplayContentsAnimator>(2); boolean mInitialized = false; @@ -120,18 +112,10 @@ public class WindowAnimator { void removeDisplayLocked(final int displayId) { final DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId); if (displayAnimator != null) { - if (displayAnimator.mWindowAnimationBackgroundSurface != null) { - displayAnimator.mWindowAnimationBackgroundSurface.destroySurface(); - displayAnimator.mWindowAnimationBackgroundSurface = null; - } if (displayAnimator.mScreenRotationAnimation != null) { displayAnimator.mScreenRotationAnimation.kill(); displayAnimator.mScreenRotationAnimation = null; } - if (displayAnimator.mDimAnimator != null) { - displayAnimator.mDimAnimator.destroySurface(); - displayAnimator.mDimAnimator = null; - } } mDisplayContentsAnimators.delete(displayId); @@ -172,28 +156,33 @@ public class WindowAnimator { } } - private void updateAppWindowsLocked() { - int i; - final ArrayList<AppWindowToken> appTokens = mService.mAnimatingAppTokens; - final int NAT = appTokens.size(); - for (i=0; i<NAT; i++) { - final AppWindowAnimator appAnimator = appTokens.get(i).mAppAnimator; - final boolean wasAnimating = appAnimator.animation != null - && appAnimator.animation != AppWindowAnimator.sDummyAnimation; - if (appAnimator.stepAnimationLocked(mCurrentTime)) { - mAnimating = true; - } else if (wasAnimating) { - // stopped animating, do one more pass through the layout - setAppLayoutChanges(appAnimator, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER, - "appToken " + appAnimator.mAppToken + " done"); - if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG, - "updateWindowsApps...: done animating " + appAnimator.mAppToken); + private void updateAppWindowsLocked(int displayId) { + final DisplayContent displayContent = mService.getDisplayContentLocked(displayId); + final ArrayList<Task> tasks = displayContent.getTasks(); + final int numTasks = tasks.size(); + for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) { + final AppTokenList tokens = tasks.get(taskNdx).mAppTokens; + final int numTokens = tokens.size(); + for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) { + final AppWindowAnimator appAnimator = tokens.get(tokenNdx).mAppAnimator; + final boolean wasAnimating = appAnimator.animation != null + && appAnimator.animation != AppWindowAnimator.sDummyAnimation; + if (appAnimator.stepAnimationLocked(mCurrentTime)) { + mAnimating = true; + } else if (wasAnimating) { + // stopped animating, do one more pass through the layout + setAppLayoutChanges(appAnimator, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER, + "appToken " + appAnimator.mAppToken + " done"); + if (WindowManagerService.DEBUG_ANIM) Slog.v(TAG, + "updateWindowsApps...: done animating " + appAnimator.mAppToken); + } } } - final int NEAT = mService.mExitingAppTokens.size(); - for (i=0; i<NEAT; i++) { - final AppWindowAnimator appAnimator = mService.mExitingAppTokens.get(i).mAppAnimator; + final AppTokenList exitingAppTokens = displayContent.mExitingAppTokens; + final int NEAT = exitingAppTokens.size(); + for (int i = 0; i < NEAT; i++) { + final AppWindowAnimator appAnimator = exitingAppTokens.get(i).mAppAnimator; final boolean wasAnimating = appAnimator.animation != null && appAnimator.animation != AppWindowAnimator.sDummyAnimation; if (appAnimator.stepAnimationLocked(mCurrentTime)) { @@ -299,10 +288,13 @@ public class WindowAnimator { wallpaperInUnForceHiding = true; } } - if (mCurrentFocus == null || mCurrentFocus.mLayer < win.mLayer) { + final WindowState currentFocus = mService.mCurrentFocus; + if (currentFocus == null || currentFocus.mLayer < win.mLayer) { // We are showing on to of the current // focus, so re-evaluate focus to make // sure it is correct. + if (WindowManagerService.DEBUG_FOCUS_LIGHT) Slog.v(TAG, + "updateWindowsLocked: setting mFocusMayChange true"); mService.mFocusMayChange = true; } } @@ -359,11 +351,9 @@ public class WindowAnimator { } private void updateWallpaperLocked(int displayId) { - final DisplayContentsAnimator displayAnimator = - getDisplayContentsAnimatorLocked(displayId); + mService.getDisplayContentLocked(displayId).resetAnimationBackgroundAnimator(); + final WindowList windows = mService.getWindowListLocked(displayId); - WindowStateAnimator windowAnimationBackground = null; - int windowAnimationBackgroundColor = 0; WindowState detachedWallpaper = null; for (int i = windows.size() - 1; i >= 0; i--) { @@ -384,13 +374,9 @@ public class WindowAnimator { && winAnimator.mAnimation.getDetachWallpaper()) { detachedWallpaper = win; } - final int backgroundColor = winAnimator.mAnimation.getBackgroundColor(); - if (backgroundColor != 0) { - if (windowAnimationBackground == null || (winAnimator.mAnimLayer < - windowAnimationBackground.mAnimLayer)) { - windowAnimationBackground = winAnimator; - windowAnimationBackgroundColor = backgroundColor; - } + final int color = winAnimator.mAnimation.getBackgroundColor(); + if (color != 0) { + win.getStack().setAnimationBackground(winAnimator, color); } } mAnimating = true; @@ -407,13 +393,9 @@ public class WindowAnimator { detachedWallpaper = win; } - final int backgroundColor = appAnimator.animation.getBackgroundColor(); - if (backgroundColor != 0) { - if (windowAnimationBackground == null || (winAnimator.mAnimLayer < - windowAnimationBackground.mAnimLayer)) { - windowAnimationBackground = winAnimator; - windowAnimationBackgroundColor = backgroundColor; - } + final int color = appAnimator.animation.getBackgroundColor(); + if (color != 0) { + win.getStack().setAnimationBackground(winAnimator, color); } } } // end forall windows @@ -425,68 +407,47 @@ public class WindowAnimator { mWindowDetachedWallpaper = detachedWallpaper; mBulkUpdateParams |= SET_WALLPAPER_MAY_CHANGE; } - - if (windowAnimationBackgroundColor != 0) { - // If the window that wants black is the current wallpaper - // target, then the black goes *below* the wallpaper so we - // don't cause the wallpaper to suddenly disappear. - int animLayer = windowAnimationBackground.mAnimLayer; - WindowState win = windowAnimationBackground.mWin; - if (mService.mWallpaperTarget == win || mService.mLowerWallpaperTarget == win - || mService.mUpperWallpaperTarget == win) { - final int N = windows.size(); - for (int i = 0; i < N; i++) { - WindowStateAnimator winAnimator = windows.get(i).mWinAnimator; - if (winAnimator.mIsWallpaper) { - animLayer = winAnimator.mAnimLayer; - break; - } - } - } - - displayAnimator.mWindowAnimationBackgroundSurface.show( - animLayer - WindowManagerService.LAYER_OFFSET_DIM, - ((windowAnimationBackgroundColor >> 24) & 0xff) / 255f, 0); - } else { - displayAnimator.mWindowAnimationBackgroundSurface.hide(); - } } /** See if any windows have been drawn, so they (and others associated with them) can now be * shown. */ - private void testTokenMayBeDrawnLocked() { + private void testTokenMayBeDrawnLocked(int displayId) { // See if any windows have been drawn, so they (and others // associated with them) can now be shown. - final ArrayList<AppWindowToken> appTokens = mService.mAnimatingAppTokens; - final int NT = appTokens.size(); - for (int i=0; i<NT; i++) { - AppWindowToken wtoken = appTokens.get(i); - AppWindowAnimator appAnimator = wtoken.mAppAnimator; - final boolean allDrawn = wtoken.allDrawn; - if (allDrawn != appAnimator.allDrawn) { - appAnimator.allDrawn = allDrawn; - if (allDrawn) { - // The token has now changed state to having all - // windows shown... what to do, what to do? - if (appAnimator.freezingScreen) { - appAnimator.showAllWindowsLocked(); - mService.unsetAppFreezingScreenLocked(wtoken, false, true); - if (WindowManagerService.DEBUG_ORIENTATION) Slog.i(TAG, - "Setting mOrientationChangeComplete=true because wtoken " - + wtoken + " numInteresting=" + wtoken.numInterestingWindows - + " numDrawn=" + wtoken.numDrawnWindows); - // This will set mOrientationChangeComplete and cause a pass through layout. - setAppLayoutChanges(appAnimator, - WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER, - "testTokenMayBeDrawnLocked: freezingScreen"); - } else { - setAppLayoutChanges(appAnimator, - WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM, - "testTokenMayBeDrawnLocked"); + final ArrayList<Task> tasks = mService.getDisplayContentLocked(displayId).getTasks(); + final int numTasks = tasks.size(); + for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) { + final AppTokenList tokens = tasks.get(taskNdx).mAppTokens; + final int numTokens = tokens.size(); + for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) { + final AppWindowToken wtoken = tokens.get(tokenNdx); + AppWindowAnimator appAnimator = wtoken.mAppAnimator; + final boolean allDrawn = wtoken.allDrawn; + if (allDrawn != appAnimator.allDrawn) { + appAnimator.allDrawn = allDrawn; + if (allDrawn) { + // The token has now changed state to having all + // windows shown... what to do, what to do? + if (appAnimator.freezingScreen) { + appAnimator.showAllWindowsLocked(); + mService.unsetAppFreezingScreenLocked(wtoken, false, true); + if (WindowManagerService.DEBUG_ORIENTATION) Slog.i(TAG, + "Setting mOrientationChangeComplete=true because wtoken " + + wtoken + " numInteresting=" + wtoken.numInterestingWindows + + " numDrawn=" + wtoken.numDrawnWindows); + // This will set mOrientationChangeComplete and cause a pass through layout. + setAppLayoutChanges(appAnimator, + WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER, + "testTokenMayBeDrawnLocked: freezingScreen"); + } else { + setAppLayoutChanges(appAnimator, + WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM, + "testTokenMayBeDrawnLocked"); - // We can now show all of the drawn windows! - if (!mService.mOpeningApps.contains(wtoken)) { - mAnimating |= appAnimator.showAllWindowsLocked(); + // We can now show all of the drawn windows! + if (!mService.mOpeningApps.contains(wtoken)) { + mAnimating |= appAnimator.showAllWindowsLocked(); + } } } } @@ -499,17 +460,6 @@ public class WindowAnimator { updateWallpaperLocked(displayId); } - private long getDimBehindFadeDuration(long duration) { - TypedValue tv = new TypedValue(); - mContext.getResources().getValue( - com.android.internal.R.fraction.config_dimBehindFadeDuration, tv, true); - if (tv.type == TypedValue.TYPE_FRACTION) { - duration = (long)tv.getFraction(duration, duration); - } else if (tv.type >= TypedValue.TYPE_FIRST_INT && tv.type <= TypedValue.TYPE_LAST_INT) { - duration = tv.data; - } - return duration; - } /** Locked on mService.mWindowMap. */ private void animateLocked() { @@ -530,11 +480,10 @@ public class WindowAnimator { SurfaceControl.openTransaction(); SurfaceControl.setAnimationTransaction(); try { - updateAppWindowsLocked(); - final int numDisplays = mDisplayContentsAnimators.size(); for (int i = 0; i < numDisplays; i++) { final int displayId = mDisplayContentsAnimators.keyAt(i); + updateAppWindowsLocked(displayId); DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i); final ScreenRotationAnimation screenRotationAnimation = @@ -560,53 +509,18 @@ public class WindowAnimator { } } - testTokenMayBeDrawnLocked(); - for (int i = 0; i < numDisplays; i++) { final int displayId = mDisplayContentsAnimators.keyAt(i); - DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.valueAt(i); + + testTokenMayBeDrawnLocked(displayId); final ScreenRotationAnimation screenRotationAnimation = - displayAnimator.mScreenRotationAnimation; + mDisplayContentsAnimators.valueAt(i).mScreenRotationAnimation; if (screenRotationAnimation != null) { screenRotationAnimation.updateSurfacesInTransaction(); } - final DimLayer dimAnimator = displayAnimator.mDimAnimator; - final WindowStateAnimator winAnimator = displayAnimator.mDimWinAnimator; - final int dimLayer; - final float dimAmount; - if (winAnimator == null) { - dimLayer = dimAnimator.getLayer(); - dimAmount = 0; - } else { - dimLayer = winAnimator.mAnimLayer - WindowManagerService.LAYER_OFFSET_DIM; - dimAmount = winAnimator.mWin.mAttrs.dimAmount; - } - final float targetAlpha = dimAnimator.getTargetAlpha(); - if (targetAlpha != dimAmount) { - if (winAnimator == null) { - dimAnimator.hide(DEFAULT_DIM_DURATION); - } else { - long duration = (winAnimator.mAnimating && winAnimator.mAnimation != null) - ? winAnimator.mAnimation.computeDurationHint() - : DEFAULT_DIM_DURATION; - if (targetAlpha > dimAmount) { - duration = getDimBehindFadeDuration(duration); - } - dimAnimator.show(dimLayer, dimAmount, duration); - } - } else if (dimAnimator.getLayer() != dimLayer) { - dimAnimator.setLayer(dimLayer); - } - if (dimAnimator.isAnimating()) { - if (!mService.okToDisplay()) { - // Jump to the end of the animation. - dimAnimator.show(); - } else { - mAnimating |= dimAnimator.stepAnimation(); - } - } + mAnimating |= mService.getDisplayContentLocked(displayId).animateDimLayers(); //TODO (multidisplay): Magnification is supported only for the default display. if (mService.mDisplayMagnifier != null && displayId == Display.DEFAULT_DISPLAY) { @@ -614,6 +528,12 @@ public class WindowAnimator { } } + if (mAnimating) { + mService.scheduleAnimationLocked(); + } + + mService.setFocusedStackLayer(); + if (mService.mWatermark != null) { mService.mWatermark.drawIfNeeded(); } @@ -647,9 +567,7 @@ public class WindowAnimator { mService.requestTraversalLocked(); } - if (mAnimating) { - mService.scheduleAnimationLocked(); - } else if (wasAnimating) { + if (!mAnimating && wasAnimating) { mService.requestTraversalLocked(); } if (WindowManagerService.DEBUG_WINDOW_TRACE) { @@ -660,26 +578,6 @@ public class WindowAnimator { } } - WindowState mCurrentFocus; - void setCurrentFocus(final WindowState currentFocus) { - mCurrentFocus = currentFocus; - } - - boolean isDimmingLocked(int displayId) { - return getDisplayContentsAnimatorLocked(displayId).mDimAnimator.isDimming(); - } - - boolean isDimmingLocked(final WindowStateAnimator winAnimator) { - final int displayId = winAnimator.mWin.getDisplayId(); - DisplayContentsAnimator displayAnimator = - getDisplayContentsAnimatorLocked(displayId); - if (displayAnimator != null) { - return displayAnimator.mDimWinAnimator == winAnimator - && displayAnimator.mDimAnimator.isDimming(); - } - return false; - } - static String bulkUpdateParamsToString(int bulkUpdateParams) { StringBuilder builder = new StringBuilder(128); if ((bulkUpdateParams & LayoutFields.SET_UPDATE_ROTATION) != 0) { @@ -717,18 +615,6 @@ public class WindowAnimator { pw.print(subPrefix); pw.print("Window #"); pw.print(j); pw.print(": "); pw.println(wanim); } - if (displayAnimator.mWindowAnimationBackgroundSurface != null) { - if (dumpAll || displayAnimator.mWindowAnimationBackgroundSurface.isDimming()) { - pw.print(subPrefix); pw.println("mWindowAnimationBackgroundSurface:"); - displayAnimator.mWindowAnimationBackgroundSurface.printTo(subSubPrefix, pw); - } - } - if (dumpAll || displayAnimator.mDimAnimator.isDimming()) { - pw.print(subPrefix); pw.println("mDimAnimator:"); - displayAnimator.mDimAnimator.printTo(subSubPrefix, pw); - pw.print(subPrefix); pw.print("mDimWinAnimator="); - pw.println(displayAnimator.mDimWinAnimator); - } if (displayAnimator.mScreenRotationAnimation != null) { pw.print(subPrefix); pw.println("mScreenRotationAnimation:"); displayAnimator.mScreenRotationAnimation.printTo(subSubPrefix, pw); @@ -771,7 +657,7 @@ public class WindowAnimator { void setAppLayoutChanges(final AppWindowAnimator appAnimator, final int changes, String s) { // Used to track which displays layout changes have been done. - SparseIntArray displays = new SparseIntArray(); + SparseIntArray displays = new SparseIntArray(2); WindowList windows = appAnimator.mAppToken.allAppWindows; for (int i = windows.size() - 1; i >= 0; i--) { final int displayId = windows.get(i).getDisplayId(); @@ -786,26 +672,10 @@ public class WindowAnimator { } } - void setDimWinAnimatorLocked(int displayId, WindowStateAnimator newWinAnimator) { - DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId); - if (newWinAnimator == null) { - displayAnimator.mDimWinAnimator = null; - } else { - // Only set dim params on the highest dimmed layer. - final WindowStateAnimator existingDimWinAnimator = displayAnimator.mDimWinAnimator; - // Don't turn on for an unshown surface, or for any layer but the highest dimmed layer. - if (newWinAnimator.mSurfaceShown && (existingDimWinAnimator == null - || !existingDimWinAnimator.mSurfaceShown - || existingDimWinAnimator.mAnimLayer < newWinAnimator.mAnimLayer)) { - displayAnimator.mDimWinAnimator = newWinAnimator; - } - } - } - private DisplayContentsAnimator getDisplayContentsAnimatorLocked(int displayId) { DisplayContentsAnimator displayAnimator = mDisplayContentsAnimators.get(displayId); if (displayAnimator == null) { - displayAnimator = new DisplayContentsAnimator(displayId); + displayAnimator = new DisplayContentsAnimator(); mDisplayContentsAnimators.put(displayId, displayAnimator); } return displayAnimator; @@ -820,14 +690,6 @@ public class WindowAnimator { } private class DisplayContentsAnimator { - DimLayer mDimAnimator = null; - WindowStateAnimator mDimWinAnimator = null; - DimLayer mWindowAnimationBackgroundSurface = null; ScreenRotationAnimation mScreenRotationAnimation = null; - - public DisplayContentsAnimator(int displayId) { - mDimAnimator = new DimLayer(mService, displayId); - mWindowAnimationBackgroundSurface = new DimLayer(mService, displayId); - } } } diff --git a/services/java/com/android/server/wm/WindowManagerService.java b/services/java/com/android/server/wm/WindowManagerService.java index 585b2e0a7de6..15bf8c83fefc 100644 --- a/services/java/com/android/server/wm/WindowManagerService.java +++ b/services/java/com/android/server/wm/WindowManagerService.java @@ -16,44 +16,27 @@ package com.android.server.wm; -import static android.view.WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW; -import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM; -import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND; -import static android.view.WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON; -import static android.view.WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE; -import static android.view.WindowManager.LayoutParams.FLAG_SHOW_WALLPAPER; -import static android.view.WindowManager.LayoutParams.LAST_APPLICATION_WINDOW; -import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; -import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; -import static android.view.WindowManager.LayoutParams.TYPE_BOOT_PROGRESS; -import static android.view.WindowManager.LayoutParams.TYPE_DREAM; -import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; -import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_KEYGUARD; -import static android.view.WindowManager.LayoutParams.TYPE_RECENTS_OVERLAY; -import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_DIALOG; -import static android.view.WindowManager.LayoutParams.TYPE_SYSTEM_ERROR; -import static android.view.WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND; -import static android.view.WindowManager.LayoutParams.TYPE_WALLPAPER; +import static android.view.WindowManager.LayoutParams.*; + +import static com.android.server.am.ActivityStackSupervisor.HOME_STACK_ID; import static android.view.WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; import android.app.AppOpsManager; import android.util.TimeUtils; import android.view.IWindowId; + import com.android.internal.app.IBatteryStats; import com.android.internal.policy.PolicyManager; import com.android.internal.policy.impl.PhoneWindowManager; +import com.android.internal.util.FastPrintWriter; import com.android.internal.view.IInputContext; import com.android.internal.view.IInputMethodClient; import com.android.internal.view.IInputMethodManager; import com.android.internal.view.WindowManagerPolicyThread; import com.android.server.AttributeCache; import com.android.server.EventLogTags; +import com.android.server.UiThread; import com.android.server.Watchdog; import com.android.server.am.BatteryStatsService; import com.android.server.display.DisplayManagerService; @@ -62,6 +45,7 @@ import com.android.server.power.PowerManagerService; import com.android.server.power.ShutdownThread; import android.Manifest; +import android.app.ActivityManager.StackBoxInfo; import android.app.ActivityManagerNative; import android.app.IActivityManager; import android.app.StatusBarManager; @@ -76,8 +60,8 @@ import android.content.pm.PackageManager; import android.content.res.CompatibilityInfo; import android.content.res.Configuration; import android.graphics.Bitmap; +import android.graphics.Bitmap.Config; import android.graphics.Canvas; -import android.graphics.Color; import android.graphics.Matrix; import android.graphics.PixelFormat; import android.graphics.Point; @@ -133,6 +117,7 @@ import android.view.InputEventReceiver; import android.view.KeyEvent; import android.view.MagnificationSpec; import android.view.MotionEvent; +import android.view.Surface.OutOfResourcesException; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceSession; @@ -143,6 +128,7 @@ import android.view.WindowManagerGlobal; import android.view.WindowManagerPolicy; import android.view.WindowManager.LayoutParams; import android.view.WindowManagerPolicy.FakeWindow; +import android.view.WindowManagerPolicy.PointerEventListener; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.view.animation.Transformation; @@ -166,7 +152,6 @@ import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.List; -import java.util.NoSuchElementException; /** {@hide} */ public class WindowManagerService extends IWindowManager.Stub @@ -176,6 +161,7 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean DEBUG = false; static final boolean DEBUG_ADD_REMOVE = false; static final boolean DEBUG_FOCUS = false; + static final boolean DEBUG_FOCUS_LIGHT = DEBUG_FOCUS || false; static final boolean DEBUG_ANIM = false; static final boolean DEBUG_LAYOUT = false; static final boolean DEBUG_RESIZE = false; @@ -200,6 +186,8 @@ public class WindowManagerService extends IWindowManager.Stub static final boolean DEBUG_LAYOUT_REPEATS = true; static final boolean DEBUG_SURFACE_TRACE = false; static final boolean DEBUG_WINDOW_TRACE = false; + static final boolean DEBUG_TASK_MOVEMENT = false; + static final boolean DEBUG_STACK = false; static final boolean SHOW_SURFACE_ALLOC = false; static final boolean SHOW_TRANSACTIONS = false; static final boolean SHOW_LIGHT_TRANSACTIONS = false || SHOW_TRANSACTIONS; @@ -234,6 +222,11 @@ public class WindowManagerService extends IWindowManager.Stub static final int LAYER_OFFSET_BLUR = 2; /** + * FocusedStackFrame layer is immediately above focused window. + */ + static final int LAYER_OFFSET_FOCUSED_STACK = 1; + + /** * Animation thumbnail is as far as possible below the window above * the thumbnail (or in other words as far as possible above the window * below it). @@ -263,6 +256,9 @@ public class WindowManagerService extends IWindowManager.Stub /** Amount of time (in milliseconds) to delay before declaring a window freeze timeout. */ static final int WINDOW_FREEZE_TIMEOUT_DURATION = 2000; + /** Amount of time (in milliseconds) to delay before declaring a starting window leaked. */ + static final int STARTING_WINDOW_TIMEOUT_DURATION = 10000; + /** * If true, the window manager will do its own custom freezing and general * management of the screen during rotation. @@ -276,6 +272,12 @@ public class WindowManagerService extends IWindowManager.Stub // Default input dispatching timeout in nanoseconds. static final long DEFAULT_INPUT_DISPATCHING_TIMEOUT_NANOS = 5000 * 1000000L; + /** Minimum value for createStack and resizeStack weight value */ + public static final float STACK_WEIGHT_MIN = 0.2f; + + /** Maximum value for createStack and resizeStack weight value */ + public static final float STACK_WEIGHT_MAX = 0.8f; + static final int UPDATE_FOCUS_NORMAL = 0; static final int UPDATE_FOCUS_WILL_ASSIGN_LAYERS = 1; static final int UPDATE_FOCUS_PLACING_SURFACES = 2; @@ -284,6 +286,9 @@ public class WindowManagerService extends IWindowManager.Stub private static final String SYSTEM_SECURE = "ro.secure"; private static final String SYSTEM_DEBUGGABLE = "ro.debuggable"; + private static final String DENSITY_OVERRIDE = "ro.config.density_override"; + private static final String SIZE_OVERRIDE = "ro.config.size_override"; + private static final int MAX_SCREENSHOT_RETRIES = 3; final private KeyguardDisableHandler mKeyguardDisableHandler; @@ -337,34 +342,7 @@ public class WindowManagerService extends IWindowManager.Stub /** * Mapping from a token IBinder to a WindowToken object. */ - final HashMap<IBinder, WindowToken> mTokenMap = - new HashMap<IBinder, WindowToken>(); - - /** - * Window tokens that are in the process of exiting, but still - * on screen for animations. - */ - final ArrayList<WindowToken> mExitingTokens = new ArrayList<WindowToken>(); - - /** - * List controlling the ordering of windows in different applications which must - * be kept in sync with ActivityManager. - */ - final ArrayList<AppWindowToken> mAppTokens = new ArrayList<AppWindowToken>(); - - /** - * AppWindowTokens in the Z order they were in at the start of an animation. Between - * animations this list is maintained in the exact order of mAppTokens. If tokens - * are added to mAppTokens during an animation an attempt is made to insert them at the same - * logical location in this list. Note that this list is always in sync with mWindows. - */ - ArrayList<AppWindowToken> mAnimatingAppTokens = new ArrayList<AppWindowToken>(); - - /** - * Application tokens that are in the process of exiting, but still - * on screen for animations. - */ - final ArrayList<AppWindowToken> mExitingAppTokens = new ArrayList<AppWindowToken>(); + final HashMap<IBinder, WindowToken> mTokenMap = new HashMap<IBinder, WindowToken>(); /** * List of window tokens that have finished starting their application, @@ -437,8 +415,12 @@ public class WindowManagerService extends IWindowManager.Stub final SurfaceSession mFxSession; Watermark mWatermark; StrictModeFlash mStrictModeFlash; + FocusedStackFrame mFocusedStackFrame; + + int mFocusedStackLayer; final float[] mTmpFloats = new float[9]; + final Rect mTmpContentRect = new Rect(); boolean mDisplayReady; boolean mSafeMode; @@ -449,8 +431,8 @@ public class WindowManagerService extends IWindowManager.Stub String mLastANRState; - /** All DisplayDontents in the world, kept here */ - SparseArray<DisplayContent> mDisplayContents = new SparseArray<DisplayContent>(); + /** All DisplayContents in the world, kept here */ + SparseArray<DisplayContent> mDisplayContents = new SparseArray<DisplayContent>(2); int mRotation = 0; int mForcedAppOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -459,7 +441,6 @@ public class WindowManagerService extends IWindowManager.Stub = new ArrayList<IRotationWatcher>(); int mDeferredRotationPauseCount; - final Rect mSystemDecorRect = new Rect(); int mSystemDecorLayer = 0; final Rect mScreenRect = new Rect(); @@ -486,7 +467,7 @@ public class WindowManagerService extends IWindowManager.Stub // This is held as long as we have the screen frozen, to give us time to // perform a rotation animation when turning off shows the lock screen which // changes the orientation. - private PowerManager.WakeLock mScreenFrozenLock; + private final PowerManager.WakeLock mScreenFrozenLock; final AppTransition mAppTransition; boolean mStartingIconInTransition = false; @@ -589,7 +570,6 @@ public class WindowManagerService extends IWindowManager.Stub Object mLastWindowFreezeSource = null; private Session mHoldScreen = null; private boolean mObscured = false; - boolean mDimming = false; private boolean mSyswin = false; private float mScreenBrightness = -1; private float mButtonBrightness = -1; @@ -604,22 +584,6 @@ public class WindowManagerService extends IWindowManager.Stub } final LayoutFields mInnerFields = new LayoutFields(); - static class AppWindowAnimParams { - AppWindowAnimator mAppAnimator; - ArrayList<WindowStateAnimator> mWinAnimators; - - public AppWindowAnimParams(final AppWindowAnimator appAnimator) { - mAppAnimator = appAnimator; - - final AppWindowToken atoken = appAnimator.mAppToken; - mWinAnimators = new ArrayList<WindowStateAnimator>(); - final int N = atoken.allAppWindows.size(); - for (int i = 0; i < N; i++) { - mWinAnimators.add(atoken.allAppWindows.get(i).mWinAnimator); - } - } - } - boolean mAnimationScheduled; /** Skip repeated AppWindowTokens initialization. Note that AppWindowsToken's version of this @@ -631,6 +595,11 @@ public class WindowManagerService extends IWindowManager.Stub final WindowAnimator mAnimator; + SparseArray<Task> mTaskIdToTask = new SparseArray<Task>(); + SparseArray<TaskStack> mStackIdToStack = new SparseArray<TaskStack>(); + + private final PointerEventDispatcher mPointerEventDispatcher; + final class DragInputEventReceiver extends InputEventReceiver { public DragInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); @@ -701,7 +670,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean mInTouchMode = true; private ViewServer mViewServer; - private ArrayList<WindowChangeListener> mWindowChangeListeners = + private final ArrayList<WindowChangeListener> mWindowChangeListeners = new ArrayList<WindowChangeListener>(); private boolean mWindowsChanged = false; @@ -722,8 +691,7 @@ public class WindowManagerService extends IWindowManager.Stub public static WindowManagerService main(final Context context, final PowerManagerService pm, final DisplayManagerService dm, - final InputManagerService im, - final Handler uiHandler, final Handler wmHandler, + final InputManagerService im, final Handler wmHandler, final boolean haveInputMethods, final boolean showBootMsgs, final boolean onlyCore) { final WindowManagerService[] holder = new WindowManagerService[1]; @@ -731,7 +699,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void run() { holder[0] = new WindowManagerService(context, pm, dm, im, - uiHandler, haveInputMethods, showBootMsgs, onlyCore); + haveInputMethods, showBootMsgs, onlyCore); } }, 0); return holder[0]; @@ -753,7 +721,6 @@ public class WindowManagerService extends IWindowManager.Stub private WindowManagerService(Context context, PowerManagerService pm, DisplayManagerService displayManager, InputManagerService inputManager, - Handler uiHandler, boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore) { mContext = context; mHaveInputMethods = haveInputMethods; @@ -761,11 +728,15 @@ public class WindowManagerService extends IWindowManager.Stub mOnlyCore = onlyCore; mLimitedAlphaCompositing = context.getResources().getBoolean( com.android.internal.R.bool.config_sf_limitedAlpha); + mInputManager = inputManager; // Must be before createDisplayContentLocked. mDisplayManagerService = displayManager; mHeadless = displayManager.isHeadless(); mDisplaySettings = new DisplaySettings(context); mDisplaySettings.readSettingsLocked(); + mPointerEventDispatcher = new PointerEventDispatcher(mInputManager.monitorInput(TAG)); + + mFxSession = new SurfaceSession(); mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE); mDisplayManager.registerDisplayListener(this, null); Display[] displays = mDisplayManager.getDisplays(); @@ -787,9 +758,9 @@ public class WindowManagerService extends IWindowManager.Stub mBatteryStats = BatteryStatsService.getService(); mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE); mAppOps.startWatchingMode(AppOpsManager.OP_SYSTEM_ALERT_WINDOW, null, - new AppOpsManager.Callback() { + new AppOpsManager.OnOpChangedInternalListener() { @Override - public void opChanged(int op, String packageName) { + public void onOpChanged(int op, String packageName) { updateAppOpsState(); } } @@ -812,11 +783,9 @@ public class WindowManagerService extends IWindowManager.Stub | PowerManager.ON_AFTER_RELEASE, TAG); mHoldingScreenWakeLock.setReferenceCounted(false); - mInputManager = inputManager; - mFxSession = new SurfaceSession(); mAnimator = new WindowAnimator(this); - initPolicy(uiHandler); + initPolicy(UiThread.getHandler()); // Add ourself to the Watchdog monitors. Watchdog.getInstance().addMonitor(this); @@ -824,6 +793,8 @@ public class WindowManagerService extends IWindowManager.Stub SurfaceControl.openTransaction(); try { createWatermarkInTransaction(); + mFocusedStackFrame = new FocusedStackFrame( + getDefaultDisplayContentLocked().getDisplay(), mFxSession); } finally { SurfaceControl.closeTransaction(); } @@ -842,7 +813,7 @@ public class WindowManagerService extends IWindowManager.Stub // The window manager only throws security exceptions, so let's // log all others. if (!(e instanceof SecurityException)) { - Log.wtf(TAG, "Window Manager Crash", e); + Slog.wtf(TAG, "Window Manager Crash", e); } throw e; } @@ -860,10 +831,14 @@ public class WindowManagerService extends IWindowManager.Stub private void placeWindowBefore(WindowState pos, WindowState window) { final WindowList windows = pos.getWindowList(); - final int i = windows.indexOf(pos); + int i = windows.indexOf(pos); if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v( TAG, "Adding window " + window + " at " + i + " of " + windows.size() + " (before " + pos + ")"); + if (i < 0) { + Slog.w(TAG, "placeWindowBefore: Unable to find " + pos + " in " + windows); + i = 0; + } windows.add(i, window); mWindowsChanged = true; } @@ -920,218 +895,263 @@ public class WindowManagerService extends IWindowManager.Stub return -1; } - private void addWindowToListInOrderLocked(WindowState win, boolean addToToken) { + private int addAppWindowToListLocked(final WindowState win) { final IWindow client = win.mClient; final WindowToken token = win.mToken; final DisplayContent displayContent = win.mDisplayContent; final WindowList windows = win.getWindowList(); final int N = windows.size(); - final WindowState attached = win.mAttachedWindow; - int i; WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent); - if (attached == null) { - int tokenWindowsPos = 0; - int windowListPos = tokenWindowList.size(); - if (token.appWindowToken != null) { - int index = windowListPos - 1; - if (index >= 0) { - // If this application has existing windows, we - // simply place the new window on top of them... but - // keep the starting window on top. - if (win.mAttrs.type == TYPE_BASE_APPLICATION) { - // Base windows go behind everything else. - WindowState lowestWindow = tokenWindowList.get(0); - placeWindowBefore(lowestWindow, win); - tokenWindowsPos = indexOfWinInWindowList(lowestWindow, token.windows); - } else { - AppWindowToken atoken = win.mAppToken; - WindowState lastWindow = tokenWindowList.get(index); - if (atoken != null && lastWindow == atoken.startingWindow) { - placeWindowBefore(lastWindow, win); - tokenWindowsPos = indexOfWinInWindowList(lastWindow, token.windows); - } else { - int newIdx = findIdxBasedOnAppTokens(win); - //there is a window above this one associated with the same - //apptoken note that the window could be a floating window - //that was created later or a window at the top of the list of - //windows associated with this token. - if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) { - Slog.v(TAG, "Adding window " + win + " at " - + (newIdx + 1) + " of " + N); - } - windows.add(newIdx + 1, win); - if (newIdx < 0) { - // No window from token found on win's display. - tokenWindowsPos = 0; - } else { - tokenWindowsPos = indexOfWinInWindowList( - windows.get(newIdx), token.windows) + 1; - } - mWindowsChanged = true; - } - } + int tokenWindowsPos = 0; + int windowListPos = tokenWindowList.size(); + if (!tokenWindowList.isEmpty()) { + // If this application has existing windows, we + // simply place the new window on top of them... but + // keep the starting window on top. + if (win.mAttrs.type == TYPE_BASE_APPLICATION) { + // Base windows go behind everything else. + WindowState lowestWindow = tokenWindowList.get(0); + placeWindowBefore(lowestWindow, win); + tokenWindowsPos = indexOfWinInWindowList(lowestWindow, token.windows); + } else { + AppWindowToken atoken = win.mAppToken; + WindowState lastWindow = tokenWindowList.get(windowListPos - 1); + if (atoken != null && lastWindow == atoken.startingWindow) { + placeWindowBefore(lastWindow, win); + tokenWindowsPos = indexOfWinInWindowList(lastWindow, token.windows); } else { - // No windows from this token on this display - if (localLOGV) Slog.v( - TAG, "Figuring out where to add app window " - + client.asBinder() + " (token=" + token + ")"); - // Figure out where the window should go, based on the - // order of applications. - final int NA = mAnimatingAppTokens.size(); - WindowState pos = null; - for (i=NA-1; i>=0; i--) { - AppWindowToken t = mAnimatingAppTokens.get(i); - if (t == token) { - i--; - break; - } - - // We haven't reached the token yet; if this token - // is not going to the bottom and has windows on this display, we can - // use it as an anchor for when we do reach the token. - tokenWindowList = getTokenWindowsOnDisplay(t, win.mDisplayContent); - if (!t.sendingToBottom && tokenWindowList.size() > 0) { - pos = tokenWindowList.get(0); - } - } - // We now know the index into the apps. If we found - // an app window above, that gives us the position; else - // we need to look some more. - if (pos != null) { - // Move behind any windows attached to this one. - WindowToken atoken = mTokenMap.get(pos.mClient.asBinder()); - if (atoken != null) { - tokenWindowList = - getTokenWindowsOnDisplay(atoken, win.mDisplayContent); - final int NC = tokenWindowList.size(); - if (NC > 0) { - WindowState bottom = tokenWindowList.get(0); - if (bottom.mSubLayer < 0) { - pos = bottom; - } - } - } - placeWindowBefore(pos, win); + int newIdx = findIdxBasedOnAppTokens(win); + //there is a window above this one associated with the same + //apptoken note that the window could be a floating window + //that was created later or a window at the top of the list of + //windows associated with this token. + if (DEBUG_FOCUS_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, + "not Base app: Adding window " + win + " at " + (newIdx + 1) + " of " + + N); + windows.add(newIdx + 1, win); + if (newIdx < 0) { + // No window from token found on win's display. + tokenWindowsPos = 0; } else { - // Continue looking down until we find the first - // token that has windows on this display. - while (i >= 0) { - AppWindowToken t = mAnimatingAppTokens.get(i); - tokenWindowList = getTokenWindowsOnDisplay(t, win.mDisplayContent); - final int NW = tokenWindowList.size(); - if (NW > 0) { - pos = tokenWindowList.get(NW-1); - break; - } - i--; - } - if (pos != null) { - // Move in front of any windows attached to this - // one. - WindowToken atoken = mTokenMap.get(pos.mClient.asBinder()); - if (atoken != null) { - final int NC = atoken.windows.size(); - if (NC > 0) { - WindowState top = atoken.windows.get(NC-1); - if (top.mSubLayer >= 0) { - pos = top; - } - } - } - placeWindowAfter(pos, win); - } else { - // Just search for the start of this layer. - final int myLayer = win.mBaseLayer; - for (i=0; i<N; i++) { - WindowState w = windows.get(i); - if (w.mBaseLayer > myLayer) { - break; - } - } - if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) { - Slog.v(TAG, "Adding window " + win + " at " - + i + " of " + N); - } - windows.add(i, win); - mWindowsChanged = true; - } + tokenWindowsPos = indexOfWinInWindowList( + windows.get(newIdx), token.windows) + 1; } + mWindowsChanged = true; } - } else { - // Figure out where window should go, based on layer. - final int myLayer = win.mBaseLayer; - for (i=N-1; i>=0; i--) { - if (windows.get(i).mBaseLayer <= myLayer) { - break; + } + return tokenWindowsPos; + } + + // No windows from this token on this display + if (localLOGV) Slog.v(TAG, "Figuring out where to add app window " + client.asBinder() + + " (token=" + token + ")"); + // Figure out where the window should go, based on the + // order of applications. + WindowState pos = null; + + final ArrayList<Task> tasks = displayContent.getTasks(); + int taskNdx; + int tokenNdx = -1; + for (taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { + AppTokenList tokens = tasks.get(taskNdx).mAppTokens; + for (tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) { + final AppWindowToken t = tokens.get(tokenNdx); + if (t == token) { + --tokenNdx; + if (tokenNdx < 0) { + --taskNdx; + if (taskNdx >= 0) { + tokenNdx = tasks.get(taskNdx).mAppTokens.size() - 1; + } } + break; + } + + // We haven't reached the token yet; if this token + // is not going to the bottom and has windows on this display, we can + // use it as an anchor for when we do reach the token. + tokenWindowList = getTokenWindowsOnDisplay(t, displayContent); + if (!t.sendingToBottom && tokenWindowList.size() > 0) { + pos = tokenWindowList.get(0); } - i++; - if (DEBUG_FOCUS || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v( - TAG, "Adding window " + win + " at " - + i + " of " + N); - windows.add(i, win); - mWindowsChanged = true; } + if (tokenNdx >= 0) { + // early exit + break; + } + } - if (addToToken) { - if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); - token.windows.add(tokenWindowsPos, win); + // We now know the index into the apps. If we found + // an app window above, that gives us the position; else + // we need to look some more. + if (pos != null) { + // Move behind any windows attached to this one. + WindowToken atoken = mTokenMap.get(pos.mClient.asBinder()); + if (atoken != null) { + tokenWindowList = + getTokenWindowsOnDisplay(atoken, displayContent); + final int NC = tokenWindowList.size(); + if (NC > 0) { + WindowState bottom = tokenWindowList.get(0); + if (bottom.mSubLayer < 0) { + pos = bottom; + } + } + } + placeWindowBefore(pos, win); + return tokenWindowsPos; + } + + // Continue looking down until we find the first + // token that has windows on this display. + for ( ; taskNdx >= 0; --taskNdx) { + AppTokenList tokens = tasks.get(taskNdx).mAppTokens; + for ( ; tokenNdx >= 0; --tokenNdx) { + final AppWindowToken t = tokens.get(tokenNdx); + tokenWindowList = getTokenWindowsOnDisplay(t, displayContent); + final int NW = tokenWindowList.size(); + if (NW > 0) { + pos = tokenWindowList.get(NW-1); + break; + } + } + if (tokenNdx >= 0) { + // found + break; } + } - } else { - // Figure out this window's ordering relative to the window - // it is attached to. - final int NA = tokenWindowList.size(); - final int sublayer = win.mSubLayer; - int largestSublayer = Integer.MIN_VALUE; - WindowState windowWithLargestSublayer = null; - for (i=0; i<NA; i++) { - WindowState w = tokenWindowList.get(i); - final int wSublayer = w.mSubLayer; - if (wSublayer >= largestSublayer) { - largestSublayer = wSublayer; - windowWithLargestSublayer = w; - } - if (sublayer < 0) { - // For negative sublayers, we go below all windows - // in the same sublayer. - if (wSublayer >= sublayer) { - if (addToToken) { - if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); - token.windows.add(i, win); - } - placeWindowBefore(wSublayer >= 0 ? attached : w, win); - break; - } - } else { - // For positive sublayers, we go above all windows - // in the same sublayer. - if (wSublayer > sublayer) { - if (addToToken) { - if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); - token.windows.add(i, win); - } - placeWindowBefore(w, win); - break; + if (pos != null) { + // Move in front of any windows attached to this + // one. + WindowToken atoken = mTokenMap.get(pos.mClient.asBinder()); + if (atoken != null) { + final int NC = atoken.windows.size(); + if (NC > 0) { + WindowState top = atoken.windows.get(NC-1); + if (top.mSubLayer >= 0) { + pos = top; } } } - if (i >= NA) { - if (addToToken) { - if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); - token.windows.add(win); + placeWindowAfter(pos, win); + return tokenWindowsPos; + } + + // Just search for the start of this layer. + final int myLayer = win.mBaseLayer; + int i; + for (i = 0; i < N; i++) { + WindowState w = windows.get(i); + if (w.mBaseLayer > myLayer) { + break; + } + } + if (DEBUG_FOCUS_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, + "Based on layer: Adding window " + win + " at " + i + " of " + N); + windows.add(i, win); + mWindowsChanged = true; + return tokenWindowsPos; + } + + private void addFreeWindowToListLocked(final WindowState win) { + final WindowList windows = win.getWindowList(); + + // Figure out where window should go, based on layer. + final int myLayer = win.mBaseLayer; + int i; + for (i = windows.size() - 1; i >= 0; i--) { + if (windows.get(i).mBaseLayer <= myLayer) { + break; + } + } + i++; + if (DEBUG_FOCUS_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, + "Free window: Adding window " + win + " at " + i + " of " + windows.size()); + windows.add(i, win); + mWindowsChanged = true; + } + + private void addAttachedWindowToListLocked(final WindowState win, boolean addToToken) { + final WindowToken token = win.mToken; + final DisplayContent displayContent = win.mDisplayContent; + final WindowState attached = win.mAttachedWindow; + + WindowList tokenWindowList = getTokenWindowsOnDisplay(token, displayContent); + + // Figure out this window's ordering relative to the window + // it is attached to. + final int NA = tokenWindowList.size(); + final int sublayer = win.mSubLayer; + int largestSublayer = Integer.MIN_VALUE; + WindowState windowWithLargestSublayer = null; + int i; + for (i = 0; i < NA; i++) { + WindowState w = tokenWindowList.get(i); + final int wSublayer = w.mSubLayer; + if (wSublayer >= largestSublayer) { + largestSublayer = wSublayer; + windowWithLargestSublayer = w; + } + if (sublayer < 0) { + // For negative sublayers, we go below all windows + // in the same sublayer. + if (wSublayer >= sublayer) { + if (addToToken) { + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); + token.windows.add(i, win); + } + placeWindowBefore(wSublayer >= 0 ? attached : w, win); + break; } - if (sublayer < 0) { - placeWindowBefore(attached, win); - } else { - placeWindowAfter(largestSublayer >= 0 - ? windowWithLargestSublayer - : attached, - win); + } else { + // For positive sublayers, we go above all windows + // in the same sublayer. + if (wSublayer > sublayer) { + if (addToToken) { + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); + token.windows.add(i, win); + } + placeWindowBefore(w, win); + break; } } } + if (i >= NA) { + if (addToToken) { + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); + token.windows.add(win); + } + if (sublayer < 0) { + placeWindowBefore(attached, win); + } else { + placeWindowAfter(largestSublayer >= 0 + ? windowWithLargestSublayer + : attached, + win); + } + } + } + + private void addWindowToListInOrderLocked(final WindowState win, boolean addToToken) { + if (DEBUG_FOCUS_LIGHT) Slog.d(TAG, "addWindowToListInOrderLocked: win=" + win + + " Callers=" + Debug.getCallers(4)); + if (win.mAttachedWindow == null) { + final WindowToken token = win.mToken; + int tokenWindowsPos = 0; + if (token.appWindowToken != null) { + tokenWindowsPos = addAppWindowToListLocked(win); + } else { + addFreeWindowToListLocked(win); + } + if (addToToken) { + if (DEBUG_ADD_REMOVE) Slog.v(TAG, "Adding " + win + " to " + token); + token.windows.add(tokenWindowsPos, win); + } + } else { + addAttachedWindowToListLocked(win, addToToken); + } if (win.mAppToken != null && addToToken) { win.mAppToken.allAppWindows.add(win); @@ -1172,16 +1192,15 @@ public class WindowManagerService extends IWindowManager.Stub // same display. Or even when the current IME/target are not on the same screen as the next // IME/target. For now only look for input windows on the main screen. WindowList windows = getDefaultWindowListLocked(); - final int N = windows.size(); WindowState w = null; - int i = N; - while (i > 0) { - i--; - w = windows.get(i); + int i; + for (i = windows.size() - 1; i >= 0; --i) { + WindowState win = windows.get(i); if (DEBUG_INPUT_METHOD && willMove) Slog.i(TAG, "Checking window @" + i - + " " + w + " fl=0x" + Integer.toHexString(w.mAttrs.flags)); - if (canBeImeTarget(w)) { + + " " + win + " fl=0x" + Integer.toHexString(win.mAttrs.flags)); + if (canBeImeTarget(win)) { + w = win; //Slog.i(TAG, "Putting input method here!"); // Yet more tricksyness! If this window is a "starting" @@ -1213,10 +1232,10 @@ public class WindowManagerService extends IWindowManager.Stub // the IME above it until it is completely gone so it doesn't drop // behind the dialog or its full-screen scrim. final WindowState curTarget = mInputMethodTarget; - if (curTarget != null && w != null + if (curTarget != null && curTarget.isDisplayedLw() && curTarget.isClosing() - && (curTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer)) { + && (w == null || curTarget.mWinAnimator.mAnimLayer > w.mWinAnimator.mAnimLayer)) { if (DEBUG_INPUT_METHOD) Slog.v(TAG, "Current target higher, not changing"); return windows.indexOf(curTarget) + 1; } @@ -1546,10 +1565,6 @@ public class WindowManagerService extends IWindowManager.Stub return true; } - void adjustInputMethodDialogsLocked() { - moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true)); - } - final boolean isWallpaperVisible(WindowState wallpaperTarget) { if (DEBUG_WALLPAPER) Slog.v(TAG, "Wallpaper vis: target " + wallpaperTarget + ", obscured=" + (wallpaperTarget != null ? Boolean.toString(wallpaperTarget.mObscured) : "??") @@ -1573,8 +1588,8 @@ public class WindowManagerService extends IWindowManager.Stub // TODO(multidisplay): Wallpapers on main screen only. final DisplayInfo displayInfo = getDefaultDisplayContentLocked().getDisplayInfo(); - final int dw = displayInfo.appWidth; - final int dh = displayInfo.appHeight; + final int dw = displayInfo.logicalWidth; + final int dh = displayInfo.logicalHeight; // First find top-most window that has asked to be on top of the // wallpaper; all wallpapers go behind it. @@ -1837,18 +1852,47 @@ public class WindowManagerService extends IWindowManager.Stub } } - // Now stick it in. + // Now stick it in. For apps over wallpaper keep the wallpaper at the bottommost + // layer. For keyguard over wallpaper put the wallpaper under the keyguard. + int insertionIndex = 0; + if (visible && foundW != null) { + final int type = foundW.mAttrs.type; + if (type == TYPE_KEYGUARD || type == TYPE_KEYGUARD_SCRIM) { + insertionIndex = windows.indexOf(foundW); + } + } if (DEBUG_WALLPAPER_LIGHT || DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE) { Slog.v(TAG, "Moving wallpaper " + wallpaper - + " from " + oldIndex + " to " + foundI); + + " from " + oldIndex + " to " + insertionIndex); } - windows.add(foundI, wallpaper); + windows.add(insertionIndex, wallpaper); mWindowsChanged = true; changed |= ADJUST_WALLPAPER_LAYERS_CHANGED; } } + /* + final TaskStack targetStack = + mWallpaperTarget == null ? null : mWallpaperTarget.getStack(); + if ((changed & ADJUST_WALLPAPER_LAYERS_CHANGED) != 0 && + targetStack != null && !targetStack.isHomeStack()) { + // If the wallpaper target is not on the home stack then make sure that all windows + // from other non-home stacks are above the wallpaper. + for (i = foundI - 1; i >= 0; --i) { + WindowState win = windows.get(i); + if (!win.isVisibleLw()) { + continue; + } + final TaskStack winStack = win.getStack(); + if (winStack != null && !winStack.isHomeStack() && winStack != targetStack) { + windows.remove(i); + windows.add(foundI + 1, win); + } + } + } + */ + if (targetChanged && DEBUG_WALLPAPER_LIGHT) { Slog.d(TAG, "New wallpaper: target=" + mWallpaperTarget + " lower=" + mLowerWallpaperTarget + " upper=" @@ -1967,8 +2011,8 @@ public class WindowManagerService extends IWindowManager.Stub void updateWallpaperOffsetLocked(WindowState changingTarget, boolean sync) { final DisplayContent displayContent = changingTarget.mDisplayContent; final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - final int dw = displayInfo.appWidth; - final int dh = displayInfo.appHeight; + final int dw = displayInfo.logicalWidth; + final int dh = displayInfo.logicalHeight; WindowState target = mWallpaperTarget; if (target != null) { @@ -2027,8 +2071,8 @@ public class WindowManagerService extends IWindowManager.Stub final boolean visible = isWallpaperVisible(mWallpaperTarget); final DisplayContent displayContent = mWallpaperTarget.mDisplayContent; final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - final int dw = displayInfo.appWidth; - final int dh = displayInfo.appHeight; + final int dw = displayInfo.logicalWidth; + final int dh = displayInfo.logicalHeight; int curTokenIndex = mWallpaperTokens.size(); while (curTokenIndex > 0) { @@ -2076,6 +2120,13 @@ public class WindowManagerService extends IWindowManager.Stub final DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent == null) { + Slog.w(TAG, "Attempted to add window to a display that does not exist: " + + displayId + ". Aborting."); + return WindowManagerGlobal.ADD_INVALID_DISPLAY; + } + if (!displayContent.hasAccess(session.mUid)) { + Slog.w(TAG, "Attempted to add window to a display for which the application " + + "does not have access: " + displayId + ". Aborting."); return WindowManagerGlobal.ADD_INVALID_DISPLAY; } @@ -2099,6 +2150,11 @@ public class WindowManagerService extends IWindowManager.Stub } } + if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) { + Slog.w(TAG, "Attempted to add private presentation window to a non-private display. Aborting."); + return WindowManagerGlobal.ADD_PERMISSION_DENIED; + } + boolean addToken = false; WindowToken token = mTokenMap.get(attrs.token); if (token == null) { @@ -2211,6 +2267,8 @@ public class WindowManagerService extends IWindowManager.Stub token.appWindowToken.startingWindow = win; if (DEBUG_STARTING_WINDOW) Slog.v (TAG, "addWindow: " + token.appWindowToken + " startingWindow=" + win); + Message m = mH.obtainMessage(H.REMOVE_STARTING_TIMEOUT, token.appWindowToken); + mH.sendMessageDelayed(m, STARTING_WINDOW_TIMEOUT_DURATION); } boolean imMayMove = true; @@ -2223,7 +2281,7 @@ public class WindowManagerService extends IWindowManager.Stub } else if (type == TYPE_INPUT_METHOD_DIALOG) { mInputMethodDialogs.add(win); addWindowToListInOrderLocked(win, true); - adjustInputMethodDialogsLocked(); + moveInputMethodDialogsLocked(findDesiredInputMethodWindowIndexLocked(true)); imMayMove = false; } else { addWindowToListInOrderLocked(win, true); @@ -2311,12 +2369,16 @@ public class WindowManagerService extends IWindowManager.Stub } public void removeWindowLocked(Session session, WindowState win) { + if (win.mAttrs.type == TYPE_APPLICATION_STARTING) { + if (DEBUG_STARTING_WINDOW) Slog.d(TAG, "Starting window removed " + win); + removeStartingWindowTimeout(win.mAppToken); + } - if (localLOGV || DEBUG_FOCUS) Slog.v( - TAG, "Remove " + win + " client=" - + Integer.toHexString(System.identityHashCode( - win.mClient.asBinder())) - + ", surface=" + win.mWinAnimator.mSurfaceControl); + if (localLOGV || DEBUG_FOCUS || DEBUG_FOCUS_LIGHT && win==mCurrentFocus) Slog.v( + TAG, "Remove " + win + " client=" + + Integer.toHexString(System.identityHashCode(win.mClient.asBinder())) + + ", surface=" + win.mWinAnimator.mSurfaceControl + " Callers=" + + Debug.getCallers(4)); final long origId = Binder.clearCallingIdentity(); @@ -2366,7 +2428,6 @@ public class WindowManagerService extends IWindowManager.Stub updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/); performLayoutAndPlaceSurfacesLocked(); - mInputMonitor.updateInputWindowsLw(false /*force*/); if (win.mAppToken != null) { win.mAppToken.updateReportedVisibilityLocked(); } @@ -2454,6 +2515,7 @@ public class WindowManagerService extends IWindowManager.Stub if (atoken != null) { if (atoken.startingWindow == win) { if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Nulling startingWindow " + win); + removeStartingWindowTimeout(atoken); atoken.startingWindow = null; } else if (atoken.allAppWindows.size() == 0 && atoken.startingData != null) { // If this is the last window and we had requested a starting @@ -2463,12 +2525,7 @@ public class WindowManagerService extends IWindowManager.Stub } else if (atoken.allAppWindows.size() == 1 && atoken.startingView != null) { // If this is the last window except for a starting transition // window, we need to get rid of the starting transition. - if (DEBUG_STARTING_WINDOW) { - Slog.v(TAG, "Schedule remove starting " + token - + ": no more real windows"); - } - Message m = mH.obtainMessage(H.REMOVE_STARTING, atoken); - mH.sendMessage(m); + scheduleRemoveStartingWindow(atoken); } } @@ -2495,22 +2552,19 @@ public class WindowManagerService extends IWindowManager.Stub public void updateAppOpsState() { synchronized(mWindowMap) { - boolean changed = false; - for (int i=0; i<mDisplayContents.size(); i++) { - DisplayContent display = mDisplayContents.valueAt(i); - WindowList windows = display.getWindowList(); - for (int j=0; j<windows.size(); j++) { - final WindowState win = windows.get(j); + final int numDisplays = mDisplayContents.size(); + for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + final WindowList windows = mDisplayContents.valueAt(displayNdx).getWindowList(); + final int numWindows = windows.size(); + for (int winNdx = 0; winNdx < numWindows; ++winNdx) { + final WindowState win = windows.get(winNdx); if (win.mAppOp != AppOpsManager.OP_NONE) { - changed |= win.setAppOpVisibilityLw(mAppOps.checkOpNoThrow(win.mAppOp, - win.getOwningUid(), - win.getOwningPackage()) == AppOpsManager.MODE_ALLOWED); + final int mode = mAppOps.checkOpNoThrow(win.mAppOp, win.getOwningUid(), + win.getOwningPackage()); + win.setAppOpVisibilityLw(mode == AppOpsManager.MODE_ALLOWED); } } } - if (changed) { - scheduleAnimationLocked(); - } } } @@ -2747,7 +2801,8 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_LAYOUT) Slog.v(TAG, "Relayout " + win + ": viewVisibility=" + viewVisibility + " req=" + requestedWidth + "x" + requestedHeight + " " + win.mAttrs); - win.mEnforceSizeCompat = (win.mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0; + win.mEnforceSizeCompat = + (win.mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0; if ((attrChanges & WindowManager.LayoutParams.ALPHA_CHANGED) != 0) { winAnimator.mAlpha = attrs.alpha; @@ -2946,7 +3001,7 @@ public class WindowManagerService extends IWindowManager.Stub if (toBeDisplayed && win.mIsWallpaper) { DisplayInfo displayInfo = getDefaultDisplayInfoLocked(); updateWallpaperOffsetLocked(win, - displayInfo.appWidth, displayInfo.appHeight, false); + displayInfo.logicalWidth, displayInfo.logicalHeight, false); } if (win.mAppToken != null) { win.mAppToken.updateReportedVisibilityLocked(); @@ -2967,7 +3022,7 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Relayout of " + win + ": focusMayChange=" + focusMayChange); inTouchMode = mInTouchMode; - animating = mAnimator.mAnimating; + animating = mAnimator.mAnimating && win.mWinAnimator.isAnimating(); if (animating && !mRelayoutWhileAnimating.contains(win)) { mRelayoutWhileAnimating.add(win); } @@ -3157,35 +3212,70 @@ public class WindowManagerService extends IWindowManager.Stub // Application Window Tokens // ------------------------------------------------------------- - public void validateAppTokens(List<IBinder> tokens) { - int v = tokens.size()-1; - int m = mAppTokens.size()-1; - while (v >= 0 && m >= 0) { - AppWindowToken atoken = mAppTokens.get(m); - if (atoken.removed) { - m--; - continue; + public void validateAppTokens(int stackId, List<TaskGroup> tasks) { + synchronized (mWindowMap) { + int t = tasks.size() - 1; + if (t < 0) { + Slog.w(TAG, "validateAppTokens: empty task list"); + return; } - if (tokens.get(v) != atoken.token) { - Slog.w(TAG, "Tokens out of sync: external is " + tokens.get(v) - + " @ " + v + ", internal is " + atoken.token + " @ " + m); + + TaskGroup task = tasks.get(0); + int taskId = task.taskId; + Task targetTask = mTaskIdToTask.get(taskId); + DisplayContent displayContent = targetTask.getDisplayContent(); + if (displayContent == null) { + Slog.w(TAG, "validateAppTokens: no Display for taskId=" + taskId); + return; } - v--; - m--; - } - while (v >= 0) { - Slog.w(TAG, "External token not found: " + tokens.get(v) + " @ " + v); - v--; - } - while (m >= 0) { - AppWindowToken atoken = mAppTokens.get(m); - if (!atoken.removed) { - Slog.w(TAG, "Invalid internal atoken: " + atoken.token + " @ " + m); + + final ArrayList<Task> localTasks = mStackIdToStack.get(stackId).getTasks(); + int taskNdx; + for (taskNdx = localTasks.size() - 1; taskNdx >= 0 && t >= 0; --taskNdx, --t) { + AppTokenList localTokens = localTasks.get(taskNdx).mAppTokens; + task = tasks.get(t); + List<IApplicationToken> tokens = task.tokens; + + DisplayContent lastDisplayContent = displayContent; + displayContent = mTaskIdToTask.get(taskId).getDisplayContent(); + if (displayContent != lastDisplayContent) { + Slog.w(TAG, "validateAppTokens: displayContent changed in TaskGroup list!"); + return; + } + + int tokenNdx; + int v; + for (tokenNdx = localTokens.size() - 1, v = task.tokens.size() - 1; + tokenNdx >= 0 && v >= 0; ) { + final AppWindowToken atoken = localTokens.get(tokenNdx); + if (atoken.removed) { + --tokenNdx; + continue; + } + if (tokens.get(v) != atoken.token) { + break; + } + --tokenNdx; + v--; + } + + if (tokenNdx >= 0 || v >= 0) { + break; + } + } + + if (taskNdx >= 0 || t >= 0) { + Slog.w(TAG, "validateAppTokens: Mismatch! ActivityManager=" + tasks); + Slog.w(TAG, "validateAppTokens: Mismatch! WindowManager=" + localTasks); + Slog.w(TAG, "validateAppTokens: Mismatch! Callers=" + Debug.getCallers(4)); } - m--; } } + public void validateStackOrder(Integer[] remoteStackIds) { + // TODO: + } + boolean checkCallingPermission(String permission, String func) { // Quick check: if the calling permission is me, it's all okay. if (Binder.getCallingPid() == Process.myPid()) { @@ -3246,6 +3336,7 @@ public class WindowManagerService extends IWindowManager.Stub final long origId = Binder.clearCallingIdentity(); synchronized(mWindowMap) { + DisplayContent displayContent = null; WindowToken wtoken = mTokenMap.remove(token); if (wtoken != null) { boolean delayed = false; @@ -3255,6 +3346,7 @@ public class WindowManagerService extends IWindowManager.Stub for (int i=0; i<N; i++) { WindowState win = wtoken.windows.get(i); + displayContent = win.mDisplayContent; if (win.mWinAnimator.isAnimating()) { delayed = true; @@ -3264,13 +3356,12 @@ public class WindowManagerService extends IWindowManager.Stub win.mWinAnimator.applyAnimationLocked(WindowManagerPolicy.TRANSIT_EXIT, false); //TODO (multidisplay): Magnification is supported only for the default - if (mDisplayMagnifier != null - && win.getDisplayId() == Display.DEFAULT_DISPLAY) { + if (mDisplayMagnifier != null && win.isDefaultDisplay()) { mDisplayMagnifier.onWindowTransitionLocked(win, WindowManagerPolicy.TRANSIT_EXIT); } changed = true; - win.mDisplayContent.layoutNeeded = true; + displayContent.layoutNeeded = true; } } @@ -3283,7 +3374,7 @@ public class WindowManagerService extends IWindowManager.Stub } if (delayed) { - mExitingTokens.add(wtoken); + displayContent.mExitingTokens.add(wtoken); } else if (wtoken.windowType == TYPE_WALLPAPER) { mWallpaperTokens.remove(wtoken); } @@ -3297,27 +3388,21 @@ public class WindowManagerService extends IWindowManager.Stub Binder.restoreCallingIdentity(origId); } - /** - * Find the location to insert a new AppWindowToken into the window-ordered app token list. - * Note that mAppTokens.size() == mAnimatingAppTokens.size() + 1. - * @param addPos The location the token was inserted into in mAppTokens. - * @param atoken The token to insert. - */ - private void addAppTokenToAnimating(final int addPos, final AppWindowToken atoken) { - if (addPos == 0 || addPos == mAnimatingAppTokens.size()) { - // It was inserted into the beginning or end of mAppTokens. Honor that. - mAnimatingAppTokens.add(addPos, atoken); - return; + private Task createTask(int taskId, int stackId, int userId, AppWindowToken atoken) { + final TaskStack stack = mStackIdToStack.get(stackId); + if (stack == null) { + throw new IllegalArgumentException("addAppToken: invalid stackId=" + stackId); } - // Find the item immediately above the mAppTokens insertion point and put the token - // immediately below that one in mAnimatingAppTokens. - final AppWindowToken aboveAnchor = mAppTokens.get(addPos + 1); - mAnimatingAppTokens.add(mAnimatingAppTokens.indexOf(aboveAnchor), atoken); + Task task = new Task(atoken, stack, userId); + mTaskIdToTask.put(taskId, task); + stack.addTask(task, true); + stack.getDisplayContent().moveStack(stack, true); + return task; } @Override - public void addAppToken(int addPos, IApplicationToken token, - int groupId, int requestedOrientation, boolean fullscreen, boolean showWhenLocked) { + public void addAppToken(int addPos, IApplicationToken token, int taskId, int stackId, + int requestedOrientation, boolean fullscreen, boolean showWhenLocked, int userId) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "addAppToken()")) { throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); @@ -3345,14 +3430,20 @@ public class WindowManagerService extends IWindowManager.Stub } atoken = new AppWindowToken(this, token); atoken.inputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos; - atoken.groupId = groupId; + atoken.groupId = taskId; atoken.appFullscreen = fullscreen; atoken.showWhenLocked = showWhenLocked; atoken.requestedOrientation = requestedOrientation; if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG, "addAppToken: " + atoken - + " at " + addPos); - mAppTokens.add(addPos, atoken); - addAppTokenToAnimating(addPos, atoken); + + " to stack=" + stackId + " task=" + taskId + " at " + addPos); + + Task task = mTaskIdToTask.get(taskId); + if (task == null) { + task = createTask(taskId, stackId, userId, atoken); + } else { + task.addAppToken(addPos, atoken); + } + mTokenMap.put(token.asBinder(), atoken); // Application tokens start out hidden. @@ -3371,12 +3462,20 @@ public class WindowManagerService extends IWindowManager.Stub } synchronized(mWindowMap) { - AppWindowToken atoken = findAppWindowToken(token); + final AppWindowToken atoken = findAppWindowToken(token); if (atoken == null) { Slog.w(TAG, "Attempted to set group id of non-existing app token: " + token); return; } + Task oldTask = mTaskIdToTask.get(atoken.groupId); + oldTask.removeAppToken(atoken); + atoken.groupId = groupId; + Task newTask = mTaskIdToTask.get(groupId); + if (newTask == null) { + newTask = createTask(groupId, oldTask.mStack.mStackId, oldTask.mUserId, atoken); + } + newTask.mAppTokens.add(atoken); } } @@ -3417,72 +3516,76 @@ public class WindowManagerService extends IWindowManager.Stub } public int getOrientationFromAppTokensLocked() { - int curGroup = 0; int lastOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; boolean findingBehind = false; - boolean haveGroup = false; boolean lastFullscreen = false; - for (int pos = mAppTokens.size() - 1; pos >= 0; pos--) { - AppWindowToken atoken = mAppTokens.get(pos); - - if (DEBUG_APP_ORIENTATION) Slog.v(TAG, "Checking app orientation: " + atoken); - - // if we're about to tear down this window and not seek for - // the behind activity, don't use it for orientation - if (!findingBehind - && (!atoken.hidden && atoken.hiddenRequested)) { - if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping " + atoken - + " -- going to hide"); - continue; - } + // TODO: Multi window. + DisplayContent displayContent = getDefaultDisplayContentLocked(); + final ArrayList<Task> tasks = displayContent.getTasks(); + for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { + AppTokenList tokens = tasks.get(taskNdx).mAppTokens; + final int firstToken = tokens.size() - 1; + for (int tokenNdx = firstToken; tokenNdx >= 0; --tokenNdx) { + final AppWindowToken atoken = tokens.get(tokenNdx); + + if (DEBUG_APP_ORIENTATION) Slog.v(TAG, "Checking app orientation: " + atoken); + + // if we're about to tear down this window and not seek for + // the behind activity, don't use it for orientation + if (!findingBehind + && (!atoken.hidden && atoken.hiddenRequested)) { + if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping " + atoken + + " -- going to hide"); + continue; + } - if (haveGroup == true && curGroup != atoken.groupId) { - // If we have hit a new application group, and the bottom - // of the previous group didn't explicitly say to use - // the orientation behind it, and the last app was - // full screen, then we'll stick with the - // user's orientation. - if (lastOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND - && lastFullscreen) { - if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken - + " -- end of group, return " + lastOrientation); - return lastOrientation; + if (tokenNdx == firstToken) { + // If we have hit a new Task, and the bottom + // of the previous group didn't explicitly say to use + // the orientation behind it, and the last app was + // full screen, then we'll stick with the + // user's orientation. + if (lastOrientation != ActivityInfo.SCREEN_ORIENTATION_BEHIND + && lastFullscreen) { + if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken + + " -- end of group, return " + lastOrientation); + return lastOrientation; + } } - } - // We ignore any hidden applications on the top. - if (atoken.hiddenRequested || atoken.willBeHidden) { - if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping " + atoken - + " -- hidden on top"); - continue; - } + // We ignore any hidden applications on the top. + if (atoken.hiddenRequested || atoken.willBeHidden) { + if (DEBUG_ORIENTATION) Slog.v(TAG, "Skipping " + atoken + + " -- hidden on top"); + continue; + } - if (!haveGroup) { - haveGroup = true; - curGroup = atoken.groupId; - lastOrientation = atoken.requestedOrientation; - } + if (tokenNdx == 0) { + // Last token in this task. + lastOrientation = atoken.requestedOrientation; + } - int or = atoken.requestedOrientation; - // If this application is fullscreen, and didn't explicitly say - // to use the orientation behind it, then just take whatever - // orientation it has and ignores whatever is under it. - lastFullscreen = atoken.appFullscreen; - if (lastFullscreen - && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) { - if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken - + " -- full screen, return " + or); - return or; - } - // If this application has requested an explicit orientation, - // then use it. - if (or != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED - && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) { - if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken - + " -- explicitly set, return " + or); - return or; + int or = atoken.requestedOrientation; + // If this application is fullscreen, and didn't explicitly say + // to use the orientation behind it, then just take whatever + // orientation it has and ignores whatever is under it. + lastFullscreen = atoken.appFullscreen; + if (lastFullscreen + && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) { + if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken + + " -- full screen, return " + or); + return or; + } + // If this application has requested an explicit orientation, + // then use it. + if (or != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + && or != ActivityInfo.SCREEN_ORIENTATION_BEHIND) { + if (DEBUG_ORIENTATION) Slog.v(TAG, "Done at " + atoken + + " -- explicitly set, return " + or); + return or; + } + findingBehind |= (or == ActivityInfo.SCREEN_ORIENTATION_BEHIND); } - findingBehind |= (or == ActivityInfo.SCREEN_ORIENTATION_BEHIND); } if (DEBUG_ORIENTATION) Slog.v(TAG, "No app is requesting an orientation"); return ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED; @@ -3531,9 +3634,10 @@ public class WindowManagerService extends IWindowManager.Stub if (computeScreenConfigurationLocked(mTempConfiguration)) { if (currentConfig.diff(mTempConfiguration) != 0) { mWaitingForConfig = true; - getDefaultDisplayContentLocked().layoutNeeded = true; + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + displayContent.layoutNeeded = true; int anim[] = new int[2]; - if (mAnimator.isDimmingLocked(Display.DEFAULT_DISPLAY)) { + if (displayContent.isDimming()) { anim[0] = anim[1] = 0; } else { mPolicy.selectRotationAnimationLw(anim); @@ -3633,6 +3737,52 @@ public class WindowManagerService extends IWindowManager.Stub } } + /** Call while in a Surface transaction. */ + void setFocusedStackLayer() { + mFocusedStackLayer = 0; + if (mFocusedApp != null) { + final WindowList windows = mFocusedApp.allAppWindows; + for (int i = windows.size() - 1; i >= 0; --i) { + final WindowState win = windows.get(i); + final int animLayer = win.mWinAnimator.mAnimLayer; + if (win.mAttachedWindow == null && win.isVisibleLw() && + animLayer > mFocusedStackLayer) { + mFocusedStackLayer = animLayer + LAYER_OFFSET_FOCUSED_STACK; + } + } + } + if (DEBUG_LAYERS) Slog.v(TAG, "Setting FocusedStackFrame to layer=" + + mFocusedStackLayer); + mFocusedStackFrame.setLayer(mFocusedStackLayer); + } + + void setFocusedStackFrame() { + final TaskStack stack; + if (mFocusedApp != null) { + Task task = mTaskIdToTask.get(mFocusedApp.groupId); + stack = task.mStack; + task.getDisplayContent().setTouchExcludeRegion(stack); + } else { + stack = null; + } + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> OPEN TRANSACTION setFocusedStackFrame"); + SurfaceControl.openTransaction(); + try { + if (stack == null) { + mFocusedStackFrame.setVisibility(false); + } else { + final StackBox box = stack.mStackBox; + final Rect bounds = box.mBounds; + final boolean multipleStacks = box.mParent != null; + mFocusedStackFrame.setBounds(bounds); + mFocusedStackFrame.setVisibility(multipleStacks); + } + } finally { + SurfaceControl.closeTransaction(); + if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, ">>> CLOSE TRANSACTION setFocusedStackFrame"); + } + } + @Override public void setFocusedApp(IBinder token, boolean moveFocusNow) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, @@ -3643,7 +3793,7 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { boolean changed = false; if (token == null) { - if (DEBUG_FOCUS) Slog.v(TAG, "Clearing focused app, was " + mFocusedApp); + if (DEBUG_FOCUS_LIGHT) Slog.v(TAG, "Clearing focused app, was " + mFocusedApp); changed = mFocusedApp != null; mFocusedApp = null; if (changed) { @@ -3656,9 +3806,9 @@ public class WindowManagerService extends IWindowManager.Stub return; } changed = mFocusedApp != newFocus; + if (DEBUG_FOCUS_LIGHT) Slog.v(TAG, "Set focused app to: " + newFocus + + " old focus=" + mFocusedApp + " moveFocusNow=" + moveFocusNow); mFocusedApp = newFocus; - if (DEBUG_FOCUS) Slog.v(TAG, "Set focused app to: " + mFocusedApp - + " moveFocusNow=" + moveFocusNow); if (changed) { mInputMonitor.setFocusedAppLw(newFocus); } @@ -3767,7 +3917,7 @@ public class WindowManagerService extends IWindowManager.Stub @Override public void setAppStartingWindow(IBinder token, String pkg, int theme, CompatibilityInfo compatInfo, - CharSequence nonLocalizedLabel, int labelRes, int icon, + CharSequence nonLocalizedLabel, int labelRes, int icon, int logo, int windowFlags, IBinder transferFrom, boolean createIfNeeded) { if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, "setAppStartingWindow()")) { @@ -3832,6 +3982,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_WINDOW_MOVEMENT || DEBUG_ADD_REMOVE || DEBUG_STARTING_WINDOW) { Slog.v(TAG, "Removing starting window: " + startingWindow); } + removeStartingWindowTimeout(ttoken); startingWindow.getWindowList().remove(startingWindow); mWindowsChanged = true; if (DEBUG_ADD_REMOVE) Slog.v(TAG, @@ -3968,7 +4119,7 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Creating StartingData"); mStartingIconInTransition = true; wtoken.startingData = new StartingData(pkg, theme, compatInfo, nonLocalizedLabel, - labelRes, icon, windowFlags); + labelRes, icon, logo, windowFlags); Message m = mH.obtainMessage(H.ADD_STARTING, wtoken); // Note: we really want to do sendMessageAtFrontOfQueue() because we // want to process the message ASAP, before any other queued @@ -3997,6 +4148,14 @@ public class WindowManagerService extends IWindowManager.Stub } } + public void setAppFullscreen(IBinder token, boolean toOpaque) { + AppWindowToken atoken = findAppWindowToken(token); + if (atoken != null) { + atoken.appFullscreen = toOpaque; + requestTraversal(); + } + } + boolean setTokenVisibilityLocked(AppWindowToken wtoken, WindowManager.LayoutParams lp, boolean visible, int transit, boolean performLayout) { boolean delayed = false; @@ -4346,11 +4505,13 @@ public class WindowManagerService extends IWindowManager.Stub TAG, "Removing app " + wtoken + " delayed=" + delayed + " animation=" + wtoken.mAppAnimator.animation + " animating=" + wtoken.mAppAnimator.animating); + final Task task = mTaskIdToTask.get(wtoken.groupId); + DisplayContent displayContent = task.getDisplayContent(); if (delayed) { // set the token aside because it has an active animation to be finished if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "removeAppToken make exiting: " + wtoken); - mExitingAppTokens.add(wtoken); + displayContent.mExitingAppTokens.add(wtoken); } else { // Make sure there is no animation running on this token, // so any windows associated with it will be removed as @@ -4360,15 +4521,17 @@ public class WindowManagerService extends IWindowManager.Stub } if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "removeAppToken: " + wtoken); - mAppTokens.remove(wtoken); - mAnimatingAppTokens.remove(wtoken); + + if (task.removeAppToken(wtoken)) { + mTaskIdToTask.delete(wtoken.groupId); + } wtoken.removed = true; if (wtoken.startingData != null) { startingToken = wtoken; } unsetAppFreezingScreenLocked(wtoken, true, true); if (mFocusedApp == wtoken) { - if (DEBUG_FOCUS) Slog.v(TAG, "Removing focused app token:" + wtoken); + if (DEBUG_FOCUS_LIGHT) Slog.v(TAG, "Removing focused app token:" + wtoken); mFocusedApp = null; updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/); mInputMonitor.setFocusedAppLw(null); @@ -4383,14 +4546,29 @@ public class WindowManagerService extends IWindowManager.Stub } Binder.restoreCallingIdentity(origId); - if (startingToken != null) { - if (DEBUG_STARTING_WINDOW) Slog.v(TAG, "Schedule remove starting " - + startingToken + ": app token removed"); - Message m = mH.obtainMessage(H.REMOVE_STARTING, startingToken); - mH.sendMessage(m); + // Will only remove if startingToken non null. + scheduleRemoveStartingWindow(startingToken); + } + + void removeStartingWindowTimeout(AppWindowToken wtoken) { + if (wtoken != null) { + if (DEBUG_STARTING_WINDOW) Slog.v(TAG, Debug.getCallers(1) + + ": Remove starting window timeout " + wtoken + (wtoken != null ? + " startingWindow=" + wtoken.startingWindow : "")); + mH.removeMessages(H.REMOVE_STARTING_TIMEOUT, wtoken); } } + void scheduleRemoveStartingWindow(AppWindowToken wtoken) { + if (wtoken != null && wtoken.startingWindow != null) { + if (DEBUG_STARTING_WINDOW) Slog.v(TAG, Debug.getCallers(1) + + ": Schedule remove starting " + wtoken + (wtoken != null ? + " startingWindow=" + wtoken.startingWindow : "")); + removeStartingWindowTimeout(wtoken); + Message m = mH.obtainMessage(H.REMOVE_STARTING, wtoken); + mH.sendMessage(m); + } + } private boolean tmpRemoveAppWindowsLocked(WindowToken token) { final int NW = token.windows.size(); if (NW > 0) { @@ -4413,14 +4591,19 @@ public class WindowManagerService extends IWindowManager.Stub } void dumpAppTokensLocked() { - for (int i=mAppTokens.size()-1; i>=0; i--) { - Slog.v(TAG, " #" + i + ": " + mAppTokens.get(i).token); - } - } - - void dumpAnimatingAppTokensLocked() { - for (int i=mAnimatingAppTokens.size()-1; i>=0; i--) { - Slog.v(TAG, " #" + i + ": " + mAnimatingAppTokens.get(i).token); + final int numDisplays = mDisplayContents.size(); + for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx); + Slog.v(TAG, " Display " + displayContent.getDisplayId()); + final ArrayList<Task> tasks = displayContent.getTasks(); + int i = displayContent.numTokens(); + for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { + AppTokenList tokens = tasks.get(taskNdx).mAppTokens; + for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) { + final AppWindowToken wtoken = tokens.get(tokenNdx); + Slog.v(TAG, " #" + --i + ": " + wtoken.token); + } + } } } @@ -4430,66 +4613,79 @@ public class WindowManagerService extends IWindowManager.Stub for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final WindowList windows = mDisplayContents.valueAt(displayNdx).getWindowList(); for (int winNdx = windows.size() - 1; winNdx >= 0; --winNdx) { - final WindowState w = windows.get(winNdx); - Slog.v(TAG, " #" + i++ + ": " + w); + Slog.v(TAG, " #" + i++ + ": " + windows.get(winNdx)); } } } - private int findWindowOffsetLocked(WindowList windows, int tokenPos) { - final int NW = windows.size(); - - if (tokenPos >= mAnimatingAppTokens.size()) { - int i = NW; - while (i > 0) { - i--; - WindowState win = windows.get(i); - if (win.getAppToken() != null) { - return i+1; - } - } + private int findAppWindowInsertionPointLocked(AppWindowToken target) { + final int taskId = target.groupId; + Task targetTask = mTaskIdToTask.get(taskId); + if (targetTask == null) { + Slog.w(TAG, "findAppWindowInsertionPointLocked: no Task for " + target + " taskId=" + + taskId); + return 0; } + DisplayContent displayContent = targetTask.getDisplayContent(); + if (displayContent == null) { + Slog.w(TAG, "findAppWindowInsertionPointLocked: no DisplayContent for " + target); + return 0; + } + final WindowList windows = displayContent.getWindowList(); + final int NW = windows.size(); - while (tokenPos > 0) { - // Find the first app token below the new position that has - // a window displayed. - final AppWindowToken wtoken = mAppTokens.get(tokenPos-1); - if (DEBUG_REORDER) Slog.v(TAG, "Looking for lower windows @ " - + tokenPos + " -- " + wtoken.token); - if (wtoken.sendingToBottom) { - if (DEBUG_REORDER) Slog.v(TAG, - "Skipping token -- currently sending to bottom"); - tokenPos--; + boolean found = false; + final ArrayList<Task> tasks = displayContent.getTasks(); + for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { + final Task task = tasks.get(taskNdx); + if (!found && task.taskId != taskId) { continue; } - int i = wtoken.windows.size(); - while (i > 0) { - i--; - WindowState win = wtoken.windows.get(i); - int j = win.mChildWindows.size(); - while (j > 0) { - j--; - WindowState cwin = win.mChildWindows.get(j); - if (cwin.mSubLayer >= 0) { - for (int pos=NW-1; pos>=0; pos--) { - if (windows.get(pos) == cwin) { - if (DEBUG_REORDER) Slog.v(TAG, - "Found child win @" + (pos+1)); - return pos+1; + AppTokenList tokens = task.mAppTokens; + for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) { + final AppWindowToken wtoken = tokens.get(tokenNdx); + if (!found && wtoken == target) { + found = true; + } + if (found) { + // Find the first app token below the new position that has + // a window displayed. + if (DEBUG_REORDER) Slog.v(TAG, "Looking for lower windows in " + wtoken.token); + if (wtoken.sendingToBottom) { + if (DEBUG_REORDER) Slog.v(TAG, "Skipping token -- currently sending to bottom"); + continue; + } + for (int i = wtoken.windows.size() - 1; i >= 0; --i) { + WindowState win = wtoken.windows.get(i); + for (int j = win.mChildWindows.size() - 1; j >= 0; --j) { + WindowState cwin = win.mChildWindows.get(j); + if (cwin.mSubLayer >= 0) { + for (int pos = NW - 1; pos >= 0; pos--) { + if (windows.get(pos) == cwin) { + if (DEBUG_REORDER) Slog.v(TAG, + "Found child win @" + (pos + 1)); + return pos + 1; + } + } + } + } + for (int pos = NW - 1; pos >= 0; pos--) { + if (windows.get(pos) == win) { + if (DEBUG_REORDER) Slog.v(TAG, "Found win @" + (pos + 1)); + return pos + 1; } } - } - } - for (int pos=NW-1; pos>=0; pos--) { - if (windows.get(pos) == win) { - if (DEBUG_REORDER) Slog.v(TAG, "Found win @" + (pos+1)); - return pos+1; } } } - tokenPos--; } - + // Never put an app window underneath wallpaper. + for (int pos = NW - 1; pos >= 0; pos--) { + if (windows.get(pos).mIsWallpaper) { + if (DEBUG_REORDER) Slog.v(TAG, "Found wallpaper @" + pos); + return pos + 1; + } + } return 0; } @@ -4536,198 +4732,206 @@ public class WindowManagerService extends IWindowManager.Stub return index; } - @Override - public void moveAppToken(int index, IBinder token) { - if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, - "moveAppToken()")) { - throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); - } - - synchronized(mWindowMap) { - if (DEBUG_REORDER) Slog.v(TAG, "Initial app tokens:"); - if (DEBUG_REORDER) dumpAppTokensLocked(); - final AppWindowToken wtoken = findAppWindowToken(token); - final int oldIndex = mAppTokens.indexOf(wtoken); - if (DEBUG_TOKEN_MOVEMENT || DEBUG_REORDER) Slog.v(TAG, - "Start moving token " + wtoken + " initially at " - + oldIndex); - if (oldIndex > index && mAppTransition.isTransitionSet()) { - // animation towards back has not started, copy old list for duration of animation. - mAnimatingAppTokens.clear(); - mAnimatingAppTokens.addAll(mAppTokens); - } - if (wtoken == null || !mAppTokens.remove(wtoken)) { - Slog.w(TAG, "Attempting to reorder token that doesn't exist: " - + token + " (" + wtoken + ")"); - return; - } - mAppTokens.add(index, wtoken); - if (DEBUG_REORDER) Slog.v(TAG, "Moved " + token + " to " + index + ":"); - else if (DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, "Moved " + token + " to " + index); - if (DEBUG_REORDER) dumpAppTokensLocked(); - if (!mAppTransition.isTransitionSet()) { - // Not animating, bring animating app list in line with mAppTokens. - mAnimatingAppTokens.clear(); - mAnimatingAppTokens.addAll(mAppTokens); - - // Bring window ordering, window focus and input window in line with new app token - final long origId = Binder.clearCallingIdentity(); - if (DEBUG_REORDER) Slog.v(TAG, "Removing windows in " + token + ":"); - if (DEBUG_REORDER) dumpWindowsLocked(); - if (tmpRemoveAppWindowsLocked(wtoken)) { - if (DEBUG_REORDER) Slog.v(TAG, "Adding windows back in:"); - if (DEBUG_REORDER) dumpWindowsLocked(); - final int numDisplays = mDisplayContents.size(); - for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx); - final WindowList windows = displayContent.getWindowList(); - final int pos = findWindowOffsetLocked(windows, index); - final int newPos = reAddAppWindowsLocked(displayContent, pos, wtoken); - if (pos != newPos) { - displayContent.layoutNeeded = true; - } - } - if (DEBUG_REORDER) Slog.v(TAG, "Final window list:"); - if (DEBUG_REORDER) dumpWindowsLocked(); - updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, - false /*updateInputWindows*/); - mInputMonitor.setUpdateInputWindowsNeededLw(); - performLayoutAndPlaceSurfacesLocked(); - mInputMonitor.updateInputWindowsLw(false /*force*/); - } - Binder.restoreCallingIdentity(origId); - } - } - } - - private void removeAppTokensLocked(List<IBinder> tokens) { - // XXX This should be done more efficiently! - // (take advantage of the fact that both lists should be - // ordered in the same way.) - int N = tokens.size(); - for (int i=0; i<N; i++) { - IBinder token = tokens.get(i); - final AppWindowToken wtoken = findAppWindowToken(token); - if (DEBUG_REORDER || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, - "Temporarily removing " + wtoken + " from " + mAppTokens.indexOf(wtoken)); - if (!mAppTokens.remove(wtoken)) { - Slog.w(TAG, "Attempting to reorder token that doesn't exist: " - + token + " (" + wtoken + ")"); - i--; - N--; - } - } - } + void moveStackWindowsLocked(TaskStack stack) { + DisplayContent displayContent = stack.getDisplayContent(); - private void moveAppWindowsLocked(List<IBinder> tokens, int tokenPos) { // First remove all of the windows from the list. - final int N = tokens.size(); - int i; - for (i=0; i<N; i++) { - WindowToken token = mTokenMap.get(tokens.get(i)); - if (token != null) { - tmpRemoveAppWindowsLocked(token); + final ArrayList<Task> tasks = stack.getTasks(); + final int numTasks = tasks.size(); + for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) { + AppTokenList tokens = tasks.get(taskNdx).mAppTokens; + final int numTokens = tokens.size(); + for (int tokenNdx = numTokens - 1; tokenNdx >= 0; --tokenNdx) { + tmpRemoveAppWindowsLocked(tokens.get(tokenNdx)); } } // And now add them back at the correct place. - final int numDisplays = mDisplayContents.size(); - for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx); - final WindowList windows = displayContent.getWindowList(); - // Where to start adding? - int pos = findWindowOffsetLocked(windows, tokenPos); - for (i=0; i<N; i++) { - WindowToken token = mTokenMap.get(tokens.get(i)); - if (token != null) { - final int newPos = reAddAppWindowsLocked(displayContent, pos, token); + // Where to start adding? + for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) { + AppTokenList tokens = tasks.get(taskNdx).mAppTokens; + int pos = findAppWindowInsertionPointLocked(tokens.get(0)); + final int numTokens = tokens.size(); + for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) { + final AppWindowToken wtoken = tokens.get(tokenNdx); + if (wtoken != null) { + final int newPos = reAddAppWindowsLocked(displayContent, pos, wtoken); if (newPos != pos) { displayContent.layoutNeeded = true; } pos = newPos; } } - if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, - false /*updateInputWindows*/)) { - assignLayersLocked(windows); - } } - mInputMonitor.setUpdateInputWindowsNeededLw(); - - // Note that the above updateFocusedWindowLocked used to sit here. + if (!updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES, + false /*updateInputWindows*/)) { + assignLayersLocked(displayContent.getWindowList()); + } + mInputMonitor.setUpdateInputWindowsNeededLw(); performLayoutAndPlaceSurfacesLocked(); mInputMonitor.updateInputWindowsLw(false /*force*/); //dump(); } - @Override - public void moveAppTokensToTop(List<IBinder> tokens) { - if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, - "moveAppTokensToTop()")) { - throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); + public void moveTaskToTop(int taskId) { + final long origId = Binder.clearCallingIdentity(); + try { + synchronized(mWindowMap) { + Task task = mTaskIdToTask.get(taskId); + if (task == null) { + // Normal behavior, addAppToken will be called next and task will be created. + return; + } + final TaskStack stack = task.mStack; + final DisplayContent displayContent = task.getDisplayContent(); + final boolean isHomeStackTask = stack.isHomeStack(); + if (isHomeStackTask != displayContent.homeOnTop()) { + // First move the stack itself. + displayContent.moveHomeStackBox(isHomeStackTask); + } + stack.moveTaskToTop(task); + displayContent.moveStack(stack, true); + } + } finally { + Binder.restoreCallingIdentity(origId); } + } + public void moveTaskToBottom(int taskId) { final long origId = Binder.clearCallingIdentity(); - synchronized(mWindowMap) { - removeAppTokensLocked(tokens); - final int N = tokens.size(); - for (int i=0; i<N; i++) { - AppWindowToken wt = findAppWindowToken(tokens.get(i)); - if (wt != null) { - if (DEBUG_TOKEN_MOVEMENT || DEBUG_REORDER) Slog.v(TAG, - "Adding next to top: " + wt); - mAppTokens.add(wt); - if (mAppTransition.isTransitionSet()) { - wt.sendingToBottom = false; - } + try { + synchronized(mWindowMap) { + Task task = mTaskIdToTask.get(taskId); + if (task == null) { + Slog.e(TAG, "moveTaskToBottom: taskId=" + taskId + + " not found in mTaskIdToTask"); + return; } + final TaskStack stack = task.mStack; + stack.moveTaskToBottom(task); + moveStackWindowsLocked(stack); } + } finally { + Binder.restoreCallingIdentity(origId); + } + } - mAnimatingAppTokens.clear(); - mAnimatingAppTokens.addAll(mAppTokens); - moveAppWindowsLocked(tokens, mAppTokens.size()); + /** + * Create a new TaskStack and place it next to an existing stack. + * @param stackId The unique identifier of the new stack. + * @param relativeStackBoxId The existing stack that this stack goes before or after. + * @param position One of: + * {@link StackBox#TASK_STACK_GOES_BEFORE} + * {@link StackBox#TASK_STACK_GOES_AFTER} + * {@link StackBox#TASK_STACK_GOES_ABOVE} + * {@link StackBox#TASK_STACK_GOES_BELOW} + * {@link StackBox#TASK_STACK_GOES_UNDER} + * {@link StackBox#TASK_STACK_GOES_OVER} + * @param weight Relative weight for determining how big to make the new TaskStack. + */ + public void createStack(int stackId, int relativeStackBoxId, int position, float weight) { + synchronized (mWindowMap) { + if (position <= StackBox.TASK_STACK_GOES_BELOW && + (weight < STACK_WEIGHT_MIN || weight > STACK_WEIGHT_MAX)) { + throw new IllegalArgumentException( + "createStack: weight must be between " + STACK_WEIGHT_MIN + " and " + + STACK_WEIGHT_MAX + ", weight=" + weight); + } + final int numDisplays = mDisplayContents.size(); + for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx); + TaskStack stack = displayContent.createStack(stackId, relativeStackBoxId, position, + weight); + if (stack != null) { + mStackIdToStack.put(stackId, stack); + displayContent.moveStack(stack, true); + performLayoutAndPlaceSurfacesLocked(); + return; + } + } + Slog.e(TAG, "createStack: Unable to find relativeStackBoxId=" + relativeStackBoxId); } - Binder.restoreCallingIdentity(origId); } - @Override - public void moveAppTokensToBottom(List<IBinder> tokens) { - if (!checkCallingPermission(android.Manifest.permission.MANAGE_APP_TOKENS, - "moveAppTokensToBottom()")) { - throw new SecurityException("Requires MANAGE_APP_TOKENS permission"); + public int removeStack(int stackId) { + synchronized (mWindowMap) { + final TaskStack stack = mStackIdToStack.get(stackId); + if (stack != null) { + mStackIdToStack.delete(stackId); + int nextStackId = stack.remove(); + stack.getDisplayContent().layoutNeeded = true; + requestTraversalLocked(); + return nextStackId; + } + if (DEBUG_STACK) Slog.i(TAG, "removeStack: could not find stackId=" + stackId); } + return HOME_STACK_ID; + } - final long origId = Binder.clearCallingIdentity(); - synchronized(mWindowMap) { - final int N = tokens.size(); - if (N > 0) { - // animating towards back, hang onto old list for duration of animation. - mAnimatingAppTokens.clear(); - mAnimatingAppTokens.addAll(mAppTokens); - } - removeAppTokensLocked(tokens); - int pos = 0; - for (int i=0; i<N; i++) { - AppWindowToken wt = findAppWindowToken(tokens.get(i)); - if (wt != null) { - if (DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, - "Adding next to bottom: " + wt + " at " + pos); - mAppTokens.add(pos, wt); - if (mAppTransition.isTransitionSet()) { - wt.sendingToBottom = true; - } - pos++; + public void removeTask(int taskId) { + synchronized (mWindowMap) { + Task task = mTaskIdToTask.get(taskId); + if (task == null) { + if (DEBUG_STACK) Slog.i(TAG, "removeTask: could not find taskId=" + taskId); + return; + } + final TaskStack stack = task.mStack; + stack.removeTask(task); + stack.getDisplayContent().layoutNeeded = true; + } + } + + public void addTask(int taskId, int stackId, boolean toTop) { + synchronized (mWindowMap) { + Task task = mTaskIdToTask.get(taskId); + if (task == null) { + return; + } + TaskStack stack = mStackIdToStack.get(stackId); + stack.addTask(task, toTop); + final DisplayContent displayContent = stack.getDisplayContent(); + displayContent.layoutNeeded = true; + performLayoutAndPlaceSurfacesLocked(); + } + } + + public void resizeStackBox(int stackBoxId, float weight) { + if (weight < STACK_WEIGHT_MIN || weight > STACK_WEIGHT_MAX) { + throw new IllegalArgumentException( + "resizeStack: weight must be between " + STACK_WEIGHT_MIN + " and " + + STACK_WEIGHT_MAX + ", weight=" + weight); + } + synchronized (mWindowMap) { + final int numDisplays = mDisplayContents.size(); + for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + if (mDisplayContents.valueAt(displayNdx).resizeStack(stackBoxId, weight)) { + performLayoutAndPlaceSurfacesLocked(); + return; } } + } + throw new IllegalArgumentException("resizeStack: stackBoxId " + stackBoxId + + " not found."); + } - mAnimatingAppTokens.clear(); - mAnimatingAppTokens.addAll(mAppTokens); - moveAppWindowsLocked(tokens, 0); + public ArrayList<StackBoxInfo> getStackBoxInfos() { + synchronized(mWindowMap) { + return getDefaultDisplayContentLocked().getStackBoxInfos(); } - Binder.restoreCallingIdentity(origId); + } + + public Rect getStackBounds(int stackId) { + final int numDisplays = mDisplayContents.size(); + for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + Rect bounds = mDisplayContents.valueAt(displayNdx).getStackBounds(stackId); + if (bounds != null) { + return bounds; + } + } + return null; } // ------------------------------------------------------------- @@ -4934,6 +5138,16 @@ public class WindowManagerService extends IWindowManager.Stub mAnimatorDurationScale }; } + @Override + public void registerPointerEventListener(PointerEventListener listener) { + mPointerEventDispatcher.registerInputEventListener(listener); + } + + @Override + public void unregisterPointerEventListener(PointerEventListener listener) { + mPointerEventDispatcher.unregisterInputEventListener(listener); + } + // Called by window manager policy. Not exposed externally. @Override public int getLidState() { @@ -4953,12 +5167,6 @@ public class WindowManagerService extends IWindowManager.Stub // Called by window manager policy. Not exposed externally. @Override - public InputChannel monitorInput(String inputChannelName) { - return mInputManager.monitorInput(inputChannelName); - } - - // Called by window manager policy. Not exposed externally. - @Override public void switchKeyboardLayout(int deviceId, int direction) { mInputManager.switchKeyboardLayout(deviceId, direction); } @@ -4983,8 +5191,14 @@ public class WindowManagerService extends IWindowManager.Stub mInputManager.setInputFilter(filter); } + @Override + public void setTouchExplorationEnabled(boolean enabled) { + mPolicy.setTouchExplorationEnabled(enabled); + } + public void setCurrentUser(final int newUserId) { synchronized (mWindowMap) { + int oldUserId = mCurrentUserId; mCurrentUserId = newUserId; mAppTransition.setCurrentUser(newUserId); mPolicy.setCurrentUserLw(newUserId); @@ -4993,16 +5207,8 @@ public class WindowManagerService extends IWindowManager.Stub final int numDisplays = mDisplayContents.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx); - final WindowList windows = displayContent.getWindowList(); - for (int i = 0; i < windows.size(); i++) { - final WindowState win = windows.get(i); - if (win.isHiddenFromUserLocked()) { - Slog.w(TAG, "current user violation " + newUserId + " hiding " - + win + ", attrs=" + win.mAttrs.type + ", belonging to " - + win.mOwnerUid); - win.hideLw(false); - } - } + displayContent.switchUserStacks(oldUserId, newUserId); + rebuildAppWindowListLocked(displayContent); } performLayoutAndPlaceSurfacesLocked(); } @@ -5151,8 +5357,9 @@ public class WindowManagerService extends IWindowManager.Stub if (DEBUG_SCREEN_ON || DEBUG_BOOT) Slog.i(TAG, "******************** ENABLING SCREEN!"); if (false) { StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); + PrintWriter pw = new FastPrintWriter(sw, false, 1024); this.dump(null, pw, null); + pw.flush(); Slog.i(TAG, sw.toString()); } try { @@ -5297,9 +5504,12 @@ public class WindowManagerService extends IWindowManager.Stub * @param displayId the Display to take a screenshot of. * @param width the width of the target bitmap * @param height the height of the target bitmap + * @param force565 if true the returned bitmap will be RGB_565, otherwise it + * will be the same config as the surface */ @Override - public Bitmap screenshotApplications(IBinder appToken, int displayId, int width, int height) { + public Bitmap screenshotApplications(IBinder appToken, int displayId, int width, + int height, boolean force565) { if (!checkCallingPermission(android.Manifest.permission.READ_FRAME_BUFFER, "screenshotApplications()")) { throw new SecurityException("Requires READ_FRAME_BUFFER permission"); @@ -5356,6 +5566,7 @@ public class WindowManagerService extends IWindowManager.Stub boolean including = false; appWin = null; final WindowList windows = displayContent.getWindowList(); + final Rect stackBounds = new Rect(); for (int i = windows.size() - 1; i >= 0; i--) { WindowState ws = windows.get(i); if (!ws.mHasSurface) { @@ -5378,6 +5589,7 @@ public class WindowManagerService extends IWindowManager.Stub continue; } appWin = ws; + stackBounds.set(ws.getStackBounds()); } } @@ -5402,6 +5614,7 @@ public class WindowManagerService extends IWindowManager.Stub int right = wf.right - cr.right; int bottom = wf.bottom - cr.bottom; frame.union(left, top, right, bottom); + frame.intersect(stackBounds); } if (ws.mAppToken != null && ws.mAppToken.token == appToken && @@ -5440,9 +5653,11 @@ public class WindowManagerService extends IWindowManager.Stub // Constrain thumbnail to smaller of screen width or height. Assumes aspect // of thumbnail is the same as the screen (in landscape) or square. + scale = Math.max(width / (float) fw, height / (float) fh); + /* float targetWidthScale = width / (float) fw; float targetHeightScale = height / (float) fh; - if (dw <= dh) { + if (fw <= fh) { scale = targetWidthScale; // If aspect of thumbnail is the same as the screen (in landscape), // select the slightly larger value so we fill the entire bitmap @@ -5457,6 +5672,7 @@ public class WindowManagerService extends IWindowManager.Stub scale = targetWidthScale; } } + */ // The screen shot will contain the entire screen. dw = (int)(dw*scale); @@ -5490,10 +5706,12 @@ public class WindowManagerService extends IWindowManager.Stub return null; } - Bitmap bm = Bitmap.createBitmap(width, height, rawss.getConfig()); + Bitmap bm = Bitmap.createBitmap(width, height, force565 ? Config.RGB_565 : rawss.getConfig()); + frame.scale(scale); Matrix matrix = new Matrix(); ScreenRotationAnimation.createRotationMatrix(rot, dw, dh, matrix); - matrix.postTranslate(-FloatMath.ceil(frame.left*scale), -FloatMath.ceil(frame.top*scale)); + // TODO: Test for RTL vs. LTR and use frame.right-width instead of -frame.left + matrix.postTranslate(-FloatMath.ceil(frame.left), -FloatMath.ceil(frame.top)); Canvas canvas = new Canvas(bm); canvas.drawColor(0xFF000000); canvas.drawBitmap(rawss, matrix, null); @@ -5504,14 +5722,16 @@ public class WindowManagerService extends IWindowManager.Stub int[] buffer = new int[bm.getWidth() * bm.getHeight()]; bm.getPixels(buffer, 0, bm.getWidth(), 0, 0, bm.getWidth(), bm.getHeight()); boolean allBlack = true; + final int firstColor = buffer[0]; for (int i = 0; i < buffer.length; i++) { - if (buffer[i] != Color.BLACK) { + if (buffer[i] != firstColor) { allBlack = false; break; } } if (allBlack) { - Slog.i(TAG, "Screenshot " + appWin + " was all black! mSurfaceLayer=" + + Slog.i(TAG, "Screenshot " + appWin + " was monochrome(" + + Integer.toHexString(firstColor) + ")! mSurfaceLayer=" + (appWin != null ? appWin.mWinAnimator.mSurfaceLayer : "null") + " minLayer=" + minLayer + " maxLayer=" + maxLayer); } @@ -5703,9 +5923,10 @@ public class WindowManagerService extends IWindowManager.Stub mH.removeMessages(H.WINDOW_FREEZE_TIMEOUT); mH.sendEmptyMessageDelayed(H.WINDOW_FREEZE_TIMEOUT, WINDOW_FREEZE_TIMEOUT_DURATION); mWaitingForConfig = true; - getDefaultDisplayContentLocked().layoutNeeded = true; + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + displayContent.layoutNeeded = true; final int[] anim = new int[2]; - if (mAnimator.isDimmingLocked(Display.DEFAULT_DISPLAY)) { + if (displayContent.isDimming()) { anim[0] = anim[1] = 0; } else { mPolicy.selectRotationAnimationLw(anim); @@ -5723,7 +5944,6 @@ public class WindowManagerService extends IWindowManager.Stub // the rotation animation for the new rotation. computeScreenConfigurationLocked(null); - final DisplayContent displayContent = getDefaultDisplayContentLocked(); final DisplayInfo displayInfo = displayContent.getDisplayInfo(); if (!inTransaction) { if (SHOW_TRANSACTIONS) { @@ -6435,8 +6655,9 @@ public class WindowManagerService extends IWindowManager.Stub displayInfo.logicalDensityDpi = displayContent.mBaseDisplayDensity; displayInfo.appWidth = appWidth; displayInfo.appHeight = appHeight; - displayInfo.getLogicalMetrics(mRealDisplayMetrics, null); - displayInfo.getAppMetrics(mDisplayMetrics, null); + displayInfo.getLogicalMetrics(mRealDisplayMetrics, + CompatibilityInfo.DEFAULT_COMPATIBILITY_INFO, null); + displayInfo.getAppMetrics(mDisplayMetrics); mDisplayManagerService.setDisplayInfoOverrideFromWindowManager( displayContent.getDisplayId(), displayInfo); } @@ -6607,7 +6828,7 @@ public class WindowManagerService extends IWindowManager.Stub } else { Slog.w(TAG, "Drag already in progress"); } - } catch (SurfaceControl.OutOfResourcesException e) { + } catch (OutOfResourcesException e) { Slog.e(TAG, "Can't allocate drag surface w=" + width + " h=" + height, e); if (mDragState != null) { mDragState.reset(); @@ -6742,15 +6963,18 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { final DisplayContent displayContent = getDefaultDisplayContentLocked(); readForcedDisplaySizeAndDensityLocked(displayContent); - mDisplayReady = true; + } + + try { + mActivityManager.updateConfiguration(null); + } catch (RemoteException e) { + } + + synchronized(mWindowMap) { mIsTouchDevice = mContext.getPackageManager().hasSystemFeature( PackageManager.FEATURE_TOUCHSCREEN); - - mPolicy.setInitialDisplaySize(displayContent.getDisplay(), - displayContent.mInitialDisplayWidth, - displayContent.mInitialDisplayHeight, - displayContent.mInitialDisplayDensity); + configureDisplayPolicyLocked(getDefaultDisplayContentLocked()); } try { @@ -6777,6 +7001,8 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.mBaseDisplayWidth = displayContent.mInitialDisplayWidth; displayContent.mBaseDisplayHeight = displayContent.mInitialDisplayHeight; displayContent.mBaseDisplayDensity = displayContent.mInitialDisplayDensity; + displayContent.mBaseDisplayRect.set(0, 0, + displayContent.mBaseDisplayWidth, displayContent.mBaseDisplayHeight); } } } @@ -6838,6 +7064,10 @@ public class WindowManagerService extends IWindowManager.Stub public static final int DO_DISPLAY_CHANGED = 29; public static final int CLIENT_FREEZE_TIMEOUT = 30; + public static final int TAP_OUTSIDE_STACK = 31; + public static final int NOTIFY_ACTIVITY_DRAWN = 32; + + public static final int REMOVE_STARTING_TIMEOUT = 33; @Override public void handleMessage(Message msg) { @@ -6857,8 +7087,8 @@ public class WindowManagerService extends IWindowManager.Stub return; } mLastFocus = newFocus; - //Slog.i(TAG, "Focus moving from " + lastFocus - // + " to " + newFocus); + if (DEBUG_FOCUS_LIGHT) Slog.i(TAG, "Focus moving from " + lastFocus + + " to " + newFocus); if (newFocus != null && lastFocus != null && !newFocus.isDisplayedLw()) { //Slog.i(TAG, "Delaying loss of focus..."); @@ -6867,19 +7097,17 @@ public class WindowManagerService extends IWindowManager.Stub } } - if (lastFocus != newFocus) { - //System.out.println("Changing focus from " + lastFocus - // + " to " + newFocus); - if (newFocus != null) { - //Slog.i(TAG, "Gaining focus: " + newFocus); - newFocus.reportFocusChangedSerialized(true, mInTouchMode); - notifyFocusChanged(); - } + //System.out.println("Changing focus from " + lastFocus + // + " to " + newFocus); + if (newFocus != null) { + if (DEBUG_FOCUS_LIGHT) Slog.i(TAG, "Gaining focus: " + newFocus); + newFocus.reportFocusChangedSerialized(true, mInTouchMode); + notifyFocusChanged(); + } - if (lastFocus != null) { - //Slog.i(TAG, "Losing focus: " + lastFocus); - lastFocus.reportFocusChangedSerialized(false, mInTouchMode); - } + if (lastFocus != null) { + if (DEBUG_FOCUS_LIGHT) Slog.i(TAG, "Losing focus: " + lastFocus); + lastFocus.reportFocusChangedSerialized(false, mInTouchMode); } } break; @@ -6893,7 +7121,8 @@ public class WindowManagerService extends IWindowManager.Stub final int N = losers.size(); for (int i=0; i<N; i++) { - //Slog.i(TAG, "Losing delayed focus: " + losers.get(i)); + if (DEBUG_FOCUS_LIGHT) Slog.i(TAG, "Losing delayed focus: " + + losers.get(i)); losers.get(i).reportFocusChangedSerialized(false, mInTouchMode); } } break; @@ -6921,7 +7150,7 @@ public class WindowManagerService extends IWindowManager.Stub try { view = mPolicy.addStartingWindow( wtoken.token, sd.pkg, sd.theme, sd.compatInfo, - sd.nonLocalizedLabel, sd.labelRes, sd.icon, sd.windowFlags); + sd.nonLocalizedLabel, sd.labelRes, sd.icon, sd.logo, sd.windowFlags); } catch (Exception e) { Slog.w(TAG, "Exception when adding starting window", e); } @@ -6938,6 +7167,7 @@ public class WindowManagerService extends IWindowManager.Stub "Aborted starting " + wtoken + ": removed=" + wtoken.removed + " startingData=" + wtoken.startingData); + removeStartingWindowTimeout(wtoken); wtoken.startingWindow = null; wtoken.startingData = null; abort = true; @@ -6962,6 +7192,11 @@ public class WindowManagerService extends IWindowManager.Stub } } break; + case REMOVE_STARTING_TIMEOUT: { + final AppWindowToken wtoken = (AppWindowToken)msg.obj; + Slog.e(TAG, "Starting window " + wtoken + " timed out"); + // Fall through. + } case REMOVE_STARTING: { final AppWindowToken wtoken = (AppWindowToken)msg.obj; IBinder token = null; @@ -7082,8 +7317,6 @@ public class WindowManagerService extends IWindowManager.Stub if (mAppTransition.isTransitionSet()) { if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "*** APP TRANSITION TIMEOUT"); mAppTransition.setTimeout(); - mAnimatingAppTokens.clear(); - mAnimatingAppTokens.addAll(mAppTokens); performLayoutAndPlaceSurfacesLocked(); } } @@ -7128,13 +7361,16 @@ public class WindowManagerService extends IWindowManager.Stub case APP_FREEZE_TIMEOUT: { synchronized (mWindowMap) { Slog.w(TAG, "App freeze timeout expired."); - int i = mAppTokens.size(); - while (i > 0) { - i--; - AppWindowToken tok = mAppTokens.get(i); - if (tok.mAppAnimator.freezingScreen) { - Slog.w(TAG, "Force clearing freeze: " + tok); - unsetAppFreezingScreenLocked(tok, true, true); + DisplayContent displayContent = getDefaultDisplayContentLocked(); + final ArrayList<Task> tasks = displayContent.getTasks(); + for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { + AppTokenList tokens = tasks.get(taskNdx).mAppTokens; + for (int tokenNdx = tokens.size() - 1; tokenNdx >= 0; --tokenNdx) { + AppWindowToken tok = tokens.get(tokenNdx); + if (tok.mAppAnimator.freezingScreen) { + Slog.w(TAG, "Force clearing freeze: " + tok); + unsetAppFreezingScreenLocked(tok, true, true); + } } } } @@ -7256,6 +7492,26 @@ public class WindowManagerService extends IWindowManager.Stub handleDisplayChangedLocked(msg.arg1); } break; + + case TAP_OUTSIDE_STACK: { + int stackId; + synchronized (mWindowMap) { + stackId = ((DisplayContent)msg.obj).stackIdFromPoint(msg.arg1, msg.arg2); + } + if (stackId >= 0) { + try { + mActivityManager.setFocusedStack(stackId); + } catch (RemoteException e) { + } + } + } + break; + case NOTIFY_ACTIVITY_DRAWN: + try { + mActivityManager.notifyActivityDrawn((IBinder) msg.obj); + } catch (RemoteException e) { + } + break; } if (DEBUG_WINDOW_TRACE) { Slog.v(TAG, "handleMessage: exit"); @@ -7341,7 +7597,7 @@ public class WindowManagerService extends IWindowManager.Stub public void getInitialDisplaySize(int displayId, Point size) { synchronized (mWindowMap) { final DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent != null) { + if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) { synchronized(displayContent.mDisplaySizeLock) { size.x = displayContent.mInitialDisplayWidth; size.y = displayContent.mInitialDisplayHeight; @@ -7354,7 +7610,7 @@ public class WindowManagerService extends IWindowManager.Stub public void getBaseDisplaySize(int displayId, Point size) { synchronized (mWindowMap) { final DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent != null) { + if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) { synchronized(displayContent.mDisplaySizeLock) { size.x = displayContent.mBaseDisplayWidth; size.y = displayContent.mBaseDisplayHeight; @@ -7394,8 +7650,11 @@ public class WindowManagerService extends IWindowManager.Stub } private void readForcedDisplaySizeAndDensityLocked(final DisplayContent displayContent) { - final String sizeStr = Settings.Global.getString(mContext.getContentResolver(), + String sizeStr = Settings.Global.getString(mContext.getContentResolver(), Settings.Global.DISPLAY_SIZE_FORCED); + if (sizeStr == null || sizeStr.length() == 0) { + sizeStr = SystemProperties.get(SIZE_OVERRIDE, null); + } if (sizeStr != null && sizeStr.length() > 0) { final int pos = sizeStr.indexOf(','); if (pos > 0 && sizeStr.lastIndexOf(',') == pos) { @@ -7415,8 +7674,11 @@ public class WindowManagerService extends IWindowManager.Stub } } } - final String densityStr = Settings.Global.getString(mContext.getContentResolver(), + String densityStr = Settings.Global.getString(mContext.getContentResolver(), Settings.Global.DISPLAY_DENSITY_FORCED); + if (densityStr == null || densityStr.length() == 0) { + densityStr = SystemProperties.get(DENSITY_OVERRIDE, null); + } if (densityStr != null && densityStr.length() > 0) { int density; try { @@ -7469,7 +7731,7 @@ public class WindowManagerService extends IWindowManager.Stub public int getInitialDisplayDensity(int displayId) { synchronized (mWindowMap) { final DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent != null) { + if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) { synchronized(displayContent.mDisplaySizeLock) { return displayContent.mInitialDisplayDensity; } @@ -7482,7 +7744,7 @@ public class WindowManagerService extends IWindowManager.Stub public int getBaseDisplayDensity(int displayId) { synchronized (mWindowMap) { final DisplayContent displayContent = getDisplayContentLocked(displayId); - if (displayContent != null) { + if (displayContent != null && displayContent.hasAccess(Binder.getCallingUid())) { synchronized(displayContent.mDisplaySizeLock) { return displayContent.mBaseDisplayDensity; } @@ -7546,11 +7808,7 @@ public class WindowManagerService extends IWindowManager.Stub // displayContent must not be null private void reconfigureDisplayLocked(DisplayContent displayContent) { // TODO: Multidisplay: for now only use with default display. - mPolicy.setInitialDisplaySize(displayContent.getDisplay(), - displayContent.mBaseDisplayWidth, - displayContent.mBaseDisplayHeight, - displayContent.mBaseDisplayDensity); - + configureDisplayPolicyLocked(displayContent); displayContent.layoutNeeded = true; boolean configChanged = updateOrientationFromAppTokensLocked(false); @@ -7571,6 +7829,18 @@ public class WindowManagerService extends IWindowManager.Stub performLayoutAndPlaceSurfacesLocked(); } + private void configureDisplayPolicyLocked(DisplayContent displayContent) { + mPolicy.setInitialDisplaySize(displayContent.getDisplay(), + displayContent.mBaseDisplayWidth, + displayContent.mBaseDisplayHeight, + displayContent.mBaseDisplayDensity); + + DisplayInfo displayInfo = displayContent.getDisplayInfo(); + mPolicy.setDisplayOverscan(displayContent.getDisplay(), + displayInfo.overscanLeft, displayInfo.overscanTop, + displayInfo.overscanRight, displayInfo.overscanBottom); + } + @Override public void setOverscan(int displayId, int left, int top, int right, int bottom) { if (mContext.checkCallingOrSelfPermission( @@ -7582,26 +7852,25 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent != null) { - mDisplayManagerService.setOverscan(displayId, left, top, right, bottom); - final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - synchronized(displayContent.mDisplaySizeLock) { - displayInfo.overscanLeft = left; - displayInfo.overscanTop = top; - displayInfo.overscanRight = right; - displayInfo.overscanBottom = bottom; - } - mPolicy.setDisplayOverscan(displayContent.getDisplay(), left, top, right, bottom); - displayContent.layoutNeeded = true; - mDisplaySettings.setOverscanLocked(displayInfo.name, left, top, right, bottom); - mDisplaySettings.writeSettingsLocked(); - performLayoutAndPlaceSurfacesLocked(); + setOverscanLocked(displayContent, left, top, right, bottom); } } } - @Override - public boolean hasSystemNavBar() { - return mPolicy.hasSystemNavBar(); + private void setOverscanLocked(DisplayContent displayContent, + int left, int top, int right, int bottom) { + final DisplayInfo displayInfo = displayContent.getDisplayInfo(); + synchronized (displayContent.mDisplaySizeLock) { + displayInfo.overscanLeft = left; + displayInfo.overscanTop = top; + displayInfo.overscanRight = right; + displayInfo.overscanBottom = bottom; + } + + mDisplaySettings.setOverscanLocked(displayInfo.name, left, top, right, bottom); + mDisplaySettings.writeSettingsLocked(); + + reconfigureDisplayLocked(displayContent); } // ------------------------------------------------------------- @@ -7642,10 +7911,8 @@ public class WindowManagerService extends IWindowManager.Stub } final void rebuildAppWindowListLocked() { - final int numDisplays = mDisplayContents.size(); - for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - rebuildAppWindowListLocked(mDisplayContents.valueAt(displayNdx)); - } + // TODO: Multidisplay, when ActivityStacks and tasks exist on more than one display. + rebuildAppWindowListLocked(getDefaultDisplayContentLocked()); } private void rebuildAppWindowListLocked(final DisplayContent displayContent) { @@ -7668,8 +7935,7 @@ public class WindowManagerService extends IWindowManager.Stub win.mRebuilding = true; mRebuildTmp[numRemoved] = win; mWindowsChanged = true; - if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, - "Rebuild removing window: " + win); + if (DEBUG_WINDOW_MOVEMENT) Slog.v(TAG, "Rebuild removing window: " + win); NW--; numRemoved++; continue; @@ -7690,26 +7956,33 @@ public class WindowManagerService extends IWindowManager.Stub // in the main app list, but still have windows shown. We put them // in the back because now that the animation is over we no longer // will care about them. - int NT = mExitingAppTokens.size(); + AppTokenList exitingAppTokens = displayContent.mExitingAppTokens; + int NT = exitingAppTokens.size(); for (int j=0; j<NT; j++) { - i = reAddAppWindowsLocked(displayContent, i, mExitingAppTokens.get(j)); + i = reAddAppWindowsLocked(displayContent, i, exitingAppTokens.get(j)); } // And add in the still active app tokens in Z order. - NT = mAnimatingAppTokens.size(); - for (int j=0; j<NT; j++) { - i = reAddAppWindowsLocked(displayContent, i, mAnimatingAppTokens.get(j)); + final ArrayList<Task> tasks = displayContent.getTasks(); + final int numTasks = tasks.size(); + for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) { + final AppTokenList tokens = tasks.get(taskNdx).mAppTokens; + final int numTokens = tokens.size(); + for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) { + final AppWindowToken wtoken = tokens.get(tokenNdx); + i = reAddAppWindowsLocked(displayContent, i, wtoken); + } } i -= lastBelow; if (i != numRemoved) { - Slog.w(TAG, "Rebuild removed " + numRemoved - + " windows but added " + i); + Slog.w(TAG, "Rebuild removed " + numRemoved + " windows but added " + i, + new RuntimeException("here").fillInStackTrace()); for (i=0; i<numRemoved; i++) { WindowState ws = mRebuildTmp[i]; if (ws.mRebuilding) { StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); + PrintWriter pw = new FastPrintWriter(sw, false, 1024); ws.dump(pw, "", true); pw.flush(); Slog.w(TAG, "This window was lost: " + ws); @@ -7718,7 +7991,7 @@ public class WindowManagerService extends IWindowManager.Stub } } Slog.w(TAG, "Current app token list:"); - dumpAnimatingAppTokensLocked(); + dumpAppTokensLocked(); Slog.w(TAG, "Final window list:"); dumpWindowsLocked(); } @@ -7730,11 +8003,8 @@ public class WindowManagerService extends IWindowManager.Stub int curLayer = 0; int i; - if (DEBUG_LAYERS) { - RuntimeException here = new RuntimeException("here"); - here.fillInStackTrace(); - Slog.v(TAG, "Assigning layers", here); - } + if (DEBUG_LAYERS) Slog.v(TAG, "Assigning layers based on windows=" + windows, + new RuntimeException("here").fillInStackTrace()); boolean anyLayerChanged = false; @@ -7755,13 +8025,14 @@ public class WindowManagerService extends IWindowManager.Stub layerChanged = true; anyLayerChanged = true; } + final AppWindowToken wtoken = w.mAppToken; oldLayer = winAnimator.mAnimLayer; if (w.mTargetAppToken != null) { winAnimator.mAnimLayer = w.mLayer + w.mTargetAppToken.mAppAnimator.animLayerAdjustment; - } else if (w.mAppToken != null) { + } else if (wtoken != null) { winAnimator.mAnimLayer = - w.mLayer + w.mAppToken.mAppAnimator.animLayerAdjustment; + w.mLayer + wtoken.mAppAnimator.animLayerAdjustment; } else { winAnimator.mAnimLayer = w.mLayer; } @@ -7774,15 +8045,15 @@ public class WindowManagerService extends IWindowManager.Stub layerChanged = true; anyLayerChanged = true; } - if (layerChanged && mAnimator.isDimmingLocked(winAnimator)) { - // Force an animation pass just to update the mDimAnimator layer. + if (layerChanged && w.getStack().isDimming(winAnimator)) { + // Force an animation pass just to update the mDimLayer layer. scheduleAnimationLocked(); } if (DEBUG_LAYERS) Slog.v(TAG, "Assign layer " + w + ": " + "mBase=" + w.mBaseLayer + " mLayer=" + w.mLayer - + (w.mAppToken == null ? - "" : " mAppLayer=" + w.mAppToken.mAppAnimator.animLayerAdjustment) + + (wtoken == null ? + "" : " mAppLayer=" + wtoken.mAppAnimator.animLayerAdjustment) + " =mAnimLayer=" + winAnimator.mAnimLayer); //System.out.println( // "Assigned layer " + curLayer + " to " + w.mClient.asBinder()); @@ -7916,10 +8187,13 @@ public class WindowManagerService extends IWindowManager.Stub mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation); if (isDefaultDisplay) { // Not needed on non-default displays. - mSystemDecorLayer = mPolicy.getSystemDecorRectLw(mSystemDecorRect); + mSystemDecorLayer = mPolicy.getSystemDecorLayerLw(); mScreenRect.set(0, 0, dw, dh); } + mPolicy.getContentRectLw(mTmpContentRect); + displayContent.setStackBoxSize(mTmpContentRect); + int seq = mLayoutSeq+1; if (seq < 0) seq = 0; mLayoutSeq = seq; @@ -8230,6 +8504,8 @@ public class WindowManagerService extends IWindowManager.Stub // example, when this transition is being done behind // the lock screen. if (!mPolicy.allowAppAnimationsLw()) { + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, + "Animations disallowed by keyguard or dream."); animLp = null; } @@ -8272,8 +8548,7 @@ public class WindowManagerService extends IWindowManager.Stub NN = mClosingApps.size(); for (i=0; i<NN; i++) { AppWindowToken wtoken = mClosingApps.get(i); - if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, - "Now closing app " + wtoken); + if (DEBUG_APP_TRANSITIONS) Slog.v(TAG, "Now closing app " + wtoken); wtoken.mAppAnimator.clearThumbnail(); wtoken.inPendingTransaction = false; wtoken.mAppAnimator.animation = null; @@ -8325,12 +8600,8 @@ public class WindowManagerService extends IWindowManager.Stub mAppTransition.getStartingPoint(p); appAnimator.thumbnailX = p.x; appAnimator.thumbnailY = p.y; - } catch (SurfaceControl.OutOfResourcesException e) { - Slog.e(TAG, "Can't allocate thumbnail surface w=" + dirty.width() - + " h=" + dirty.height(), e); - appAnimator.clearThumbnail(); - } catch (Surface.OutOfResourcesException e) { - Slog.e(TAG, "Can't allocate Canvas surface w=" + dirty.width() + } catch (OutOfResourcesException e) { + Slog.e(TAG, "Can't allocate thumbnail/Canvas surface w=" + dirty.width() + " h=" + dirty.height(), e); appAnimator.clearThumbnail(); } @@ -8369,11 +8640,17 @@ public class WindowManagerService extends IWindowManager.Stub mAppTransition.setIdle(); // Restore window app tokens to the ActivityManager views - for (int i = mAnimatingAppTokens.size() - 1; i >= 0; i--) { - mAnimatingAppTokens.get(i).sendingToBottom = false; + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + final ArrayList<Task> tasks = displayContent.getTasks(); + final int numTasks = tasks.size(); + for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) { + final AppTokenList tokens = tasks.get(taskNdx).mAppTokens; + final int numTokens = tokens.size(); + for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) { + final AppWindowToken wtoken = tokens.get(tokenNdx); + wtoken.sendingToBottom = false; + } } - mAnimatingAppTokens.clear(); - mAnimatingAppTokens.addAll(mAppTokens); rebuildAppWindowListLocked(); changes |= PhoneWindowManager.FINISH_LAYOUT_REDO_LAYOUT; @@ -8412,7 +8689,7 @@ public class WindowManagerService extends IWindowManager.Stub || winAnimator.mSurfaceResized || configChanged) { if (DEBUG_RESIZE || DEBUG_ORIENTATION) { - Slog.v(TAG, "Resize reasons: " + Slog.v(TAG, "Resize reasons for w=" + w + ": " + " contentInsetsChanged=" + w.mContentInsetsChanged + " " + w.mContentInsets.toShortString() + " visibleInsetsChanged=" + w.mVisibleInsetsChanged @@ -8523,30 +8800,36 @@ public class WindowManagerService extends IWindowManager.Stub if ((attrs.flags & FLAG_DIM_BEHIND) != 0 && w.isDisplayedLw() && !w.mExiting) { - mInnerFields.mDimming = true; final WindowStateAnimator winAnimator = w.mWinAnimator; - if (!mAnimator.isDimmingLocked(winAnimator)) { + final TaskStack stack = w.getStack(); + stack.setDimmingTag(); + if (!stack.isDimming(winAnimator)) { if (localLOGV) Slog.v(TAG, "Win " + w + " start dimming."); - startDimmingLocked(winAnimator, w.mExiting ? 0 : w.mAttrs.dimAmount); + stack.startDimmingIfNeeded(winAnimator); } } } - private void updateAllDrawnLocked() { + private void updateAllDrawnLocked(DisplayContent displayContent) { // See if any windows have been drawn, so they (and others // associated with them) can now be shown. - final ArrayList<AppWindowToken> appTokens = mAnimatingAppTokens; - final int NT = appTokens.size(); - for (int i=0; i<NT; i++) { - AppWindowToken wtoken = appTokens.get(i); - if (!wtoken.allDrawn) { - int numInteresting = wtoken.numInterestingWindows; - if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) { - if (WindowManagerService.DEBUG_VISIBILITY) Slog.v(TAG, - "allDrawn: " + wtoken - + " interesting=" + numInteresting - + " drawn=" + wtoken.numDrawnWindows); - wtoken.allDrawn = true; + final ArrayList<Task> tasks = displayContent.getTasks(); + final int numTasks = tasks.size(); + for (int taskNdx = 0; taskNdx < numTasks; ++taskNdx) { + final AppTokenList tokens = tasks.get(taskNdx).mAppTokens; + final int numTokens = tokens.size(); + for (int tokenNdx = 0; tokenNdx < numTokens; ++tokenNdx) { + final AppWindowToken wtoken = tokens.get(tokenNdx); + if (!wtoken.allDrawn) { + int numInteresting = wtoken.numInterestingWindows; + if (numInteresting > 0 && wtoken.numDrawnWindows >= numInteresting) { + if (DEBUG_VISIBILITY) Slog.v(TAG, + "allDrawn: " + wtoken + + " interesting=" + numInteresting + + " drawn=" + wtoken.numDrawnWindows); + wtoken.allDrawn = true; + mH.obtainMessage(H.NOTIFY_ACTIVITY_DRAWN, wtoken.token).sendToTarget(); + } } } } @@ -8570,13 +8853,17 @@ public class WindowManagerService extends IWindowManager.Stub } // Initialize state of exiting tokens. - for (i=mExitingTokens.size()-1; i>=0; i--) { - mExitingTokens.get(i).hasVisible = false; - } + final int numDisplays = mDisplayContents.size(); + for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx); + for (i=displayContent.mExitingTokens.size()-1; i>=0; i--) { + displayContent.mExitingTokens.get(i).hasVisible = false; + } - // Initialize state of exiting applications. - for (i=mExitingAppTokens.size()-1; i>=0; i--) { - mExitingAppTokens.get(i).hasVisible = false; + // Initialize state of exiting applications. + for (i=displayContent.mExitingAppTokens.size()-1; i>=0; i--) { + displayContent.mExitingAppTokens.get(i).hasVisible = false; + } } mInnerFields.mHoldScreen = null; @@ -8605,11 +8892,10 @@ public class WindowManagerService extends IWindowManager.Stub } boolean focusDisplayed = false; - boolean updateAllDrawn = false; - final int numDisplays = mDisplayContents.size(); - for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx); + boolean updateAllDrawn = false; WindowList windows = displayContent.getWindowList(); DisplayInfo displayInfo = displayContent.getDisplayInfo(); final int displayId = displayContent.getDisplayId(); @@ -8688,8 +8974,8 @@ public class WindowManagerService extends IWindowManager.Stub } while (displayContent.pendingLayoutChanges != 0); mInnerFields.mObscured = false; - mInnerFields.mDimming = false; mInnerFields.mSyswin = false; + displayContent.resetDimming(); // Only used if default window final boolean someoneLosingFocus = !mLosingFocus.isEmpty(); @@ -8706,7 +8992,7 @@ public class WindowManagerService extends IWindowManager.Stub handleNotObscuredLocked(w, currentTime, innerDw, innerDh); } - if (!mInnerFields.mDimming) { + if (!w.getStack().testDimmingTag()) { handleFlagDimBehind(w, innerDw, innerDh); } @@ -8754,7 +9040,7 @@ public class WindowManagerService extends IWindowManager.Stub // make this happen. displayContent.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; - if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { + if (DEBUG_LAYOUT_REPEATS) { debugLayoutRepeats( "dream and commitFinishDrawingLocked true", displayContent.pendingLayoutChanges); @@ -8766,7 +9052,7 @@ public class WindowManagerService extends IWindowManager.Stub mInnerFields.mWallpaperMayChange = true; displayContent.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; - if (WindowManagerService.DEBUG_LAYOUT_REPEATS) { + if (DEBUG_LAYOUT_REPEATS) { debugLayoutRepeats( "wallpaper and commitFinishDrawingLocked true", displayContent.pendingLayoutChanges); @@ -8792,8 +9078,7 @@ public class WindowManagerService extends IWindowManager.Stub } if ((w.isOnScreen() || winAnimator.mAttrType == TYPE_BASE_APPLICATION) && !w.mExiting && !w.mDestroying) { - if (WindowManagerService.DEBUG_VISIBILITY || - WindowManagerService.DEBUG_ORIENTATION) { + if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) { Slog.v(TAG, "Eval win " + w + ": isDrawn=" + w.isDrawnLw() + ", isAnimating=" + winAnimator.isAnimating()); if (!w.isDrawnLw()) { @@ -8810,8 +9095,7 @@ public class WindowManagerService extends IWindowManager.Stub atoken.numInterestingWindows++; if (w.isDrawnLw()) { atoken.numDrawnWindows++; - if (WindowManagerService.DEBUG_VISIBILITY || - WindowManagerService.DEBUG_ORIENTATION) Slog.v(TAG, + if (DEBUG_VISIBILITY || DEBUG_ORIENTATION) Slog.v(TAG, "tokenMayBeDrawn: " + atoken + " freezingScreen=" + atoken.mAppAnimator.freezingScreen + " mAppFreezing=" + w.mAppFreezing); @@ -8849,13 +9133,11 @@ public class WindowManagerService extends IWindowManager.Stub mDisplayManagerService.setDisplayHasContent(displayId, hasUniqueContent, true /* inTraversal, must call performTraversalInTrans... below */); - if (!mInnerFields.mDimming && mAnimator.isDimmingLocked(displayId)) { - stopDimmingLocked(displayId); - } - } + getDisplayContentLocked(displayId).stopDimmingIfNeeded(); - if (updateAllDrawn) { - updateAllDrawnLocked(); + if (updateAllDrawn) { + updateAllDrawnLocked(displayContent); + } } if (focusDisplayed) { @@ -8912,8 +9194,7 @@ public class WindowManagerService extends IWindowManager.Stub mInnerFields.mWallpaperForceHidingChanged = false; if (mInnerFields.mWallpaperMayChange) { - if (WindowManagerService.DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, - "Wallpaper may change! Adjusting"); + if (DEBUG_WALLPAPER_LIGHT) Slog.v(TAG, "Wallpaper may change! Adjusting"); defaultDisplay.pendingLayoutChanges |= WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER; if (DEBUG_LAYOUT_REPEATS) debugLayoutRepeats("WallpaperMayChange", @@ -9026,30 +9307,37 @@ public class WindowManagerService extends IWindowManager.Stub } // Time to remove any exiting tokens? - for (i=mExitingTokens.size()-1; i>=0; i--) { - WindowToken token = mExitingTokens.get(i); - if (!token.hasVisible) { - mExitingTokens.remove(i); - if (token.windowType == TYPE_WALLPAPER) { - mWallpaperTokens.remove(token); + for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { + final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx); + ArrayList<WindowToken> exitingTokens = displayContent.mExitingTokens; + for (i = exitingTokens.size() - 1; i >= 0; i--) { + WindowToken token = exitingTokens.get(i); + if (!token.hasVisible) { + exitingTokens.remove(i); + if (token.windowType == TYPE_WALLPAPER) { + mWallpaperTokens.remove(token); + } } } - } - // Time to remove any exiting applications? - for (i=mExitingAppTokens.size()-1; i>=0; i--) { - AppWindowToken token = mExitingAppTokens.get(i); - if (!token.hasVisible && !mClosingApps.contains(token)) { - // Make sure there is no animation running on this token, - // so any windows associated with it will be removed as - // soon as their animations are complete - token.mAppAnimator.clearAnimation(); - token.mAppAnimator.animating = false; - if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, - "performLayout: App token exiting now removed" + token); - mAppTokens.remove(token); - mAnimatingAppTokens.remove(token); - mExitingAppTokens.remove(i); + // Time to remove any exiting applications? + AppTokenList exitingAppTokens = displayContent.mExitingAppTokens; + for (i = exitingAppTokens.size() - 1; i >= 0; i--) { + AppWindowToken token = exitingAppTokens.get(i); + if (!token.hasVisible && !mClosingApps.contains(token)) { + // Make sure there is no animation running on this token, + // so any windows associated with it will be removed as + // soon as their animations are complete + token.mAppAnimator.clearAnimation(); + token.mAppAnimator.animating = false; + if (DEBUG_ADD_REMOVE || DEBUG_TOKEN_MOVEMENT) Slog.v(TAG, + "performLayout: App token exiting now removed" + token); + final Task task = mTaskIdToTask.get(token.groupId); + if (task != null && task.removeAppToken(token)) { + mTaskIdToTask.delete(token.groupId); + } + exitingAppTokens.remove(i); + } } } @@ -9069,7 +9357,6 @@ public class WindowManagerService extends IWindowManager.Stub defaultDisplay.layoutNeeded = true; } - final int numDisplays = mDisplayContents.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx); if (displayContent.pendingLayoutChanges != 0) { @@ -9140,6 +9427,8 @@ public class WindowManagerService extends IWindowManager.Stub } } + setFocusedStackFrame(); + // Check to see if we are now in a state where the screen should // be enabled, because the window obscured flags have changed. enableScreenIfNeededLocked(); @@ -9164,9 +9453,8 @@ public class WindowManagerService extends IWindowManager.Stub //Slog.i(TAG, "Waiting for drawn " + win + ": removed=" // + win.mRemoved + " visible=" + win.isVisibleLw() // + " shown=" + win.mSurfaceShown); - if (win.mRemoved || !win.isVisibleLw()) { - // Window has been removed or made invisible; no draw - // will now happen, so stop waiting. + if (win.mRemoved) { + // Window has been removed; no draw will now happen, so stop waiting. Slog.w(TAG, "Aborted waiting for drawn: " + pair.first); try { pair.second.sendResult(null); @@ -9201,6 +9489,7 @@ public class WindowManagerService extends IWindowManager.Stub checkDrawnWindowsLocked(); return true; } + Slog.i(TAG, "waitForWindowDrawn: win null"); } } return false; @@ -9249,14 +9538,6 @@ public class WindowManagerService extends IWindowManager.Stub } } - void startDimmingLocked(final WindowStateAnimator winAnimator, final float target) { - mAnimator.setDimWinAnimatorLocked(winAnimator.mWin.getDisplayId(), winAnimator); - } - - void stopDimmingLocked(int displayId) { - mAnimator.setDimWinAnimatorLocked(displayId, null); - } - private boolean needsLayout() { final int numDisplays = mDisplayContents.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { @@ -9303,6 +9584,20 @@ public class WindowManagerService extends IWindowManager.Stub return doRequest; } + /** If a window that has an animation specifying a colored background and the current wallpaper + * is visible, then the color goes *below* the wallpaper so we don't cause the wallpaper to + * suddenly disappear. */ + int adjustAnimationBackground(WindowStateAnimator winAnimator) { + WindowList windows = winAnimator.mWin.getWindowList(); + for (int i = windows.size() - 1; i >= 0; --i) { + WindowState testWin = windows.get(i); + if (testWin.mIsWallpaper && testWin.isVisibleNow()) { + return testWin.mWinAnimator.mAnimLayer; + } + } + return winAnimator.mAnimLayer; + } + boolean reclaimSomeSurfaceMemoryLocked(WindowStateAnimator winAnimator, String operation, boolean secure) { final SurfaceControl surface = winAnimator.mSurfaceControl; @@ -9322,6 +9617,7 @@ public class WindowManagerService extends IWindowManager.Stub // window list to make sure we haven't left any dangling surfaces // around. + Slog.i(TAG, "Out of memory for surface! Looking for leaks..."); final int numDisplays = mDisplayContents.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final WindowList windows = mDisplayContents.valueAt(displayNdx).getWindowList(); @@ -9400,6 +9696,7 @@ public class WindowManagerService extends IWindowManager.Stub winAnimator.mSurfaceShown = false; winAnimator.mSurfaceControl = null; winAnimator.mWin.mHasSurface = false; + scheduleRemoveStartingWindow(winAnimator.mWin.mAppToken); } try { @@ -9422,24 +9719,25 @@ public class WindowManagerService extends IWindowManager.Stub // change message pending. mH.removeMessages(H.REPORT_FOCUS_CHANGE); mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE); - if (localLOGV) Slog.v( - TAG, "Changing focus from " + mCurrentFocus + " to " + newFocus); + // TODO(multidisplay): Focused windows on default display only. + final DisplayContent displayContent = getDefaultDisplayContentLocked(); + final boolean imWindowChanged = moveInputMethodWindowsIfNeededLocked( + mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS + && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES); + if (imWindowChanged) { + displayContent.layoutNeeded = true; + newFocus = computeFocusedWindowLocked(); + } + + if (DEBUG_FOCUS_LIGHT || localLOGV) Slog.v(TAG, "Changing focus from " + + mCurrentFocus + " to " + newFocus + " Callers=" + Debug.getCallers(4)); final WindowState oldFocus = mCurrentFocus; mCurrentFocus = newFocus; - mAnimator.setCurrentFocus(newFocus); mLosingFocus.remove(newFocus); int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus); - // TODO(multidisplay): Focused windows on default display only. - final DisplayContent displayContent = getDefaultDisplayContentLocked(); - - final WindowState imWindow = mInputMethodWindow; - if (newFocus != imWindow && oldFocus != imWindow) { - if (moveInputMethodWindowsIfNeededLocked( - mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS && - mode != UPDATE_FOCUS_WILL_PLACE_SURFACES)) { - displayContent.layoutNeeded = true; - } + if (imWindowChanged && oldFocus != mInputMethodWindow) { + // Focus of the input method window changed. Perform layout if needed. if (mode == UPDATE_FOCUS_PLACING_SURFACES) { performLayoutLockedInner(displayContent, true /*initial*/, updateInputWindows); focusChanged &= ~WindowManagerPolicy.FINISH_LAYOUT_REDO_LAYOUT; @@ -9492,9 +9790,6 @@ public class WindowManagerService extends IWindowManager.Stub } private WindowState findFocusedWindowLocked(DisplayContent displayContent) { - int nextAppIndex = mAppTokens.size()-1; - WindowToken nextApp = nextAppIndex >= 0 ? mAppTokens.get(nextAppIndex) : null; - final WindowList windows = displayContent.getWindowList(); for (int i = windows.size() - 1; i >= 0; i--) { final WindowState win = windows.get(i); @@ -9505,51 +9800,52 @@ public class WindowManagerService extends IWindowManager.Stub + ", flags=" + win.mAttrs.flags + ", canReceive=" + win.canReceiveKeys()); - AppWindowToken thisApp = win.mAppToken; + AppWindowToken wtoken = win.mAppToken; // If this window's application has been removed, just skip it. - if (thisApp != null && (thisApp.removed || thisApp.sendingToBottom)) { - if (DEBUG_FOCUS) Slog.v(TAG, "Skipping app because " + (thisApp.removed - ? "removed" : "sendingToBottom")); + if (wtoken != null && (wtoken.removed || wtoken.sendingToBottom)) { + if (DEBUG_FOCUS) Slog.v(TAG, "Skipping " + wtoken + " because " + + (wtoken.removed ? "removed" : "sendingToBottom")); + continue; + } + + if (!win.canReceiveKeys()) { continue; } - // If there is a focused app, don't allow focus to go to any - // windows below it. If this is an application window, step - // through the app tokens until we find its app. - if (thisApp != null && nextApp != null && thisApp != nextApp - && win.mAttrs.type != TYPE_APPLICATION_STARTING) { - int origAppIndex = nextAppIndex; - while (nextAppIndex > 0) { - if (nextApp == mFocusedApp) { - // Whoops, we are below the focused app... no focus - // for you! - if (localLOGV || DEBUG_FOCUS) Slog.v( - TAG, "Reached focused app: " + mFocusedApp); - return null; - } - nextAppIndex--; - nextApp = mAppTokens.get(nextAppIndex); - if (nextApp == thisApp) { + // Descend through all of the app tokens and find the first that either matches + // win.mAppToken (return win) or mFocusedApp (return null). + if (wtoken != null && win.mAttrs.type != TYPE_APPLICATION_STARTING && + mFocusedApp != null) { + ArrayList<Task> tasks = displayContent.getTasks(); + for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) { + AppTokenList tokens = tasks.get(taskNdx).mAppTokens; + int tokenNdx = tokens.size() - 1; + for ( ; tokenNdx >= 0; --tokenNdx) { + final AppWindowToken token = tokens.get(tokenNdx); + if (wtoken == token) { + break; + } + if (mFocusedApp == token) { + // Whoops, we are below the focused app... no focus for you! + if (localLOGV || DEBUG_FOCUS_LIGHT) Slog.v(TAG, + "findFocusedWindow: Reached focused app=" + mFocusedApp); + return null; + } + } + if (tokenNdx >= 0) { + // Early exit from loop, must have found the matching token. break; } } - if (thisApp != nextApp) { - // Uh oh, the app token doesn't exist! This shouldn't - // happen, but if it does we can get totally hosed... - // so restart at the original app. - nextAppIndex = origAppIndex; - nextApp = mAppTokens.get(nextAppIndex); - } } - // Dispatch to this window if it is wants key events. - if (win.canReceiveKeys()) { - if (DEBUG_FOCUS) Slog.v( - TAG, "Found focus @ " + i + " = " + win); - return win; - } + if (DEBUG_FOCUS_LIGHT) Slog.v(TAG, "findFocusedWindow: Found new focus @ " + i + + " = " + win); + return win; } + + if (DEBUG_FOCUS_LIGHT) Slog.v(TAG, "findFocusedWindow: No focusable windows."); return null; } @@ -9598,11 +9894,8 @@ public class WindowManagerService extends IWindowManager.Stub } // TODO(multidisplay): rotation on main screen only. - final Display display = displayContent.getDisplay(); - final DisplayInfo displayInfo = displayContent.getDisplayInfo(); - screenRotationAnimation = new ScreenRotationAnimation(mContext, - display, mFxSession, inTransaction, displayInfo.logicalWidth, - displayInfo.logicalHeight, display.getRotation()); + screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent, + mFxSession, inTransaction, mPolicy.isDefaultOrientationForced()); mAnimator.setScreenRotationAnimationLocked(displayId, screenRotationAnimation); } } @@ -9650,7 +9943,7 @@ public class WindowManagerService extends IWindowManager.Stub // TODO(multidisplay): rotation on main screen only. DisplayInfo displayInfo = displayContent.getDisplayInfo(); // Get rotation animation again, with new top window - boolean isDimming = mAnimator.isDimmingLocked(Display.DEFAULT_DISPLAY); + boolean isDimming = displayContent.isDimming(); if (!mPolicy.validateRotationAnimationLw(mExitAnimId, mEnterAnimId, isDimming)) { mExitAnimId = mEnterAnimId = 0; } @@ -9815,12 +10108,13 @@ public class WindowManagerService extends IWindowManager.Stub @Override public FakeWindow addFakeWindow(Looper looper, InputEventReceiver.Factory inputEventReceiverFactory, - String name, int windowType, int layoutParamsFlags, boolean canReceiveKeys, - boolean hasFocus, boolean touchFullscreen) { + String name, int windowType, int layoutParamsFlags, int layoutParamsPrivateFlags, + boolean canReceiveKeys, boolean hasFocus, boolean touchFullscreen) { synchronized (mWindowMap) { FakeWindowImpl fw = new FakeWindowImpl(this, looper, inputEventReceiverFactory, name, windowType, - layoutParamsFlags, canReceiveKeys, hasFocus, touchFullscreen); + layoutParamsFlags, layoutParamsPrivateFlags, canReceiveKeys, + hasFocus, touchFullscreen); int i=0; while (i<mFakeWindows.size()) { if (mFakeWindows.get(i).mWindowLayer <= fw.mWindowLayer) { @@ -9869,16 +10163,6 @@ public class WindowManagerService extends IWindowManager.Stub return mSafeMode; } - @Override - public void showAssistant() { - // TODO: What permission? - if (mContext.checkCallingOrSelfPermission(android.Manifest.permission.DEVICE_POWER) - != PackageManager.PERMISSION_GRANTED) { - return; - } - mPolicy.showAssistant(); - } - void dumpPolicyLocked(PrintWriter pw, String[] args, boolean dumpAll) { pw.println("WINDOW MANAGER POLICY STATE (dumpsys window policy)"); mPolicy.dump(" ", pw, args); @@ -9920,15 +10204,6 @@ public class WindowManagerService extends IWindowManager.Stub } } } - if (mAppTokens.size() > 0) { - pw.println(); - pw.println(" Application tokens in Z order:"); - for (int i=mAppTokens.size()-1; i>=0; i--) { - pw.print(" App #"); pw.print(i); - pw.print(' '); pw.print(mAppTokens.get(i)); pw.println(":"); - mAppTokens.get(i).dump(pw, " "); - } - } if (mFinishedStarting.size() > 0) { pw.println(); pw.println(" Finishing start of application tokens:"); @@ -9944,51 +10219,6 @@ public class WindowManagerService extends IWindowManager.Stub } } } - if (mExitingTokens.size() > 0) { - pw.println(); - pw.println(" Exiting tokens:"); - for (int i=mExitingTokens.size()-1; i>=0; i--) { - WindowToken token = mExitingTokens.get(i); - pw.print(" Exiting #"); pw.print(i); - pw.print(' '); pw.print(token); - if (dumpAll) { - pw.println(':'); - token.dump(pw, " "); - } else { - pw.println(); - } - } - } - if (mExitingAppTokens.size() > 0) { - pw.println(); - pw.println(" Exiting application tokens:"); - for (int i=mExitingAppTokens.size()-1; i>=0; i--) { - WindowToken token = mExitingAppTokens.get(i); - pw.print(" Exiting App #"); pw.print(i); - pw.print(' '); pw.print(token); - if (dumpAll) { - pw.println(':'); - token.dump(pw, " "); - } else { - pw.println(); - } - } - } - if (mAppTransition.isRunning() && mAnimatingAppTokens.size() > 0) { - pw.println(); - pw.println(" Application tokens during animation:"); - for (int i=mAnimatingAppTokens.size()-1; i>=0; i--) { - WindowToken token = mAnimatingAppTokens.get(i); - pw.print(" App moving to bottom #"); pw.print(i); - pw.print(' '); pw.print(token); - if (dumpAll) { - pw.println(':'); - token.dump(pw, " "); - } else { - pw.println(); - } - } - } if (mOpeningApps.size() > 0 || mClosingApps.size() > 0) { pw.println(); if (mOpeningApps.size() > 0) { @@ -10017,7 +10247,8 @@ public class WindowManagerService extends IWindowManager.Stub if (mDisplayReady) { final int numDisplays = mDisplayContents.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - mDisplayContents.valueAt(displayNdx).dump(" ", pw); + final DisplayContent displayContent = mDisplayContents.valueAt(displayNdx); + displayContent.dump(" ", pw); } } else { pw.println(" NO DISPLAY"); @@ -10032,14 +10263,13 @@ public class WindowManagerService extends IWindowManager.Stub void dumpWindowsNoHeaderLocked(PrintWriter pw, boolean dumpAll, ArrayList<WindowState> windows) { - int j = 0; final int numDisplays = mDisplayContents.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { final WindowList windowList = mDisplayContents.valueAt(displayNdx).getWindowList(); for (int winNdx = windowList.size() - 1; winNdx >= 0; --winNdx) { final WindowState w = windowList.get(winNdx); if (windows == null || windows.contains(w)) { - pw.print(" Window #"); pw.print(j++); pw.print(' '); + pw.print(" Window #"); pw.print(winNdx); pw.print(' '); pw.print(w); pw.println(":"); w.dump(pw, " ", dumpAll || windows != null); } @@ -10167,8 +10397,7 @@ public class WindowManagerService extends IWindowManager.Stub } pw.println(); if (dumpAll) { - pw.print(" mSystemDecorRect="); pw.print(mSystemDecorRect.toShortString()); - pw.print(" mSystemDecorLayer="); pw.print(mSystemDecorLayer); + pw.print(" mSystemDecorLayer="); pw.print(mSystemDecorLayer); pw.print(" mScreenRect="); pw.println(mScreenRect.toShortString()); if (mLastStatusBarVisibility != 0) { pw.print(" mLastStatusBarVisibility=0x"); @@ -10253,7 +10482,8 @@ public class WindowManagerService extends IWindowManager.Stub synchronized(mWindowMap) { final int numDisplays = mDisplayContents.size(); for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) { - final WindowList windowList = mDisplayContents.valueAt(displayNdx).getWindowList(); + final WindowList windowList = + mDisplayContents.valueAt(displayNdx).getWindowList(); for (int winNdx = windowList.size() - 1; winNdx >= 0; --winNdx) { final WindowState w = windowList.get(winNdx); if (name != null) { @@ -10294,10 +10524,12 @@ public class WindowManagerService extends IWindowManager.Stub * * @param appWindowToken The application that ANR'd, may be null. * @param windowState The window that ANR'd, may be null. + * @param reason The reason for the ANR, may be null. */ - public void saveANRStateLocked(AppWindowToken appWindowToken, WindowState windowState) { + public void saveANRStateLocked(AppWindowToken appWindowToken, WindowState windowState, + String reason) { StringWriter sw = new StringWriter(); - PrintWriter pw = new PrintWriter(sw); + PrintWriter pw = new FastPrintWriter(sw, false, 1024); pw.println(" ANR time: " + DateFormat.getInstance().format(new Date())); if (appWindowToken != null) { pw.println(" Application at fault: " + appWindowToken.stringName); @@ -10305,6 +10537,9 @@ public class WindowManagerService extends IWindowManager.Stub if (windowState != null) { pw.println(" Window at fault: " + windowState.mAttrs.getTitle()); } + if (reason != null) { + pw.println(" Reason: " + reason); + } pw.println(); dumpWindowsNoHeaderLocked(pw, true, null); pw.close(); @@ -10466,19 +10701,29 @@ public class WindowManagerService extends IWindowManager.Stub } private DisplayContent newDisplayContentLocked(final Display display) { - DisplayContent displayContent = new DisplayContent(display); - mDisplayContents.put(display.getDisplayId(), displayContent); + DisplayContent displayContent = new DisplayContent(display, this); + final int displayId = display.getDisplayId(); + mDisplayContents.put(displayId, displayContent); + + DisplayInfo displayInfo = displayContent.getDisplayInfo(); final Rect rect = new Rect(); - DisplayInfo info = displayContent.getDisplayInfo(); - mDisplaySettings.getOverscanLocked(info.name, rect); - info.overscanLeft = rect.left; - info.overscanTop = rect.top; - info.overscanRight = rect.right; - info.overscanBottom = rect.bottom; - mDisplayManagerService.setOverscan(display.getDisplayId(), rect.left, rect.top, - rect.right, rect.bottom); - mPolicy.setDisplayOverscan(displayContent.getDisplay(), rect.left, rect.top, - rect.right, rect.bottom); + mDisplaySettings.getOverscanLocked(displayInfo.name, rect); + synchronized (displayContent.mDisplaySizeLock) { + displayInfo.overscanLeft = rect.left; + displayInfo.overscanTop = rect.top; + displayInfo.overscanRight = rect.right; + displayInfo.overscanBottom = rect.bottom; + mDisplayManagerService.setDisplayInfoOverrideFromWindowManager( + displayId, displayInfo); + } + configureDisplayPolicyLocked(displayContent); + + // TODO: Create an input channel for each display with touch capability. + if (displayId == Display.DEFAULT_DISPLAY) { + displayContent.mTapDetector = new StackTapPointerEventListener(this, displayContent); + registerPointerEventListener(displayContent.mTapDetector); + } + return displayContent; } @@ -10486,7 +10731,7 @@ public class WindowManagerService extends IWindowManager.Stub if (display == null) { throw new IllegalArgumentException("getDisplayContent: display must not be null"); } - newDisplayContentLocked(display); + getDisplayContentLocked(display.getDisplayId()); } /** @@ -10560,6 +10805,10 @@ public class WindowManagerService extends IWindowManager.Stub final DisplayContent displayContent = getDisplayContentLocked(displayId); if (displayContent != null) { mDisplayContents.delete(displayId); + displayContent.close(); + if (displayId == Display.DEFAULT_DISPLAY) { + unregisterPointerEventListener(displayContent.mTapDetector); + } WindowList windows = displayContent.getWindowList(); while (!windows.isEmpty()) { final WindowState win = windows.get(windows.size() - 1); @@ -10580,4 +10829,9 @@ public class WindowManagerService extends IWindowManager.Stub displayContent.updateDisplayInfo(); } } + + @Override + public Object getWindowManagerLock() { + return mWindowMap; + } } diff --git a/services/java/com/android/server/wm/WindowState.java b/services/java/com/android/server/wm/WindowState.java index da1585660a98..2d087925e56b 100644 --- a/services/java/com/android/server/wm/WindowState.java +++ b/services/java/com/android/server/wm/WindowState.java @@ -16,8 +16,11 @@ package com.android.server.wm; +import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY; +import static com.android.server.wm.WindowManagerService.DEBUG_LAYOUT; + import static android.view.WindowManager.LayoutParams.FIRST_SUB_WINDOW; -import static android.view.WindowManager.LayoutParams.FLAG_COMPATIBLE_WINDOW; +import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW; import static android.view.WindowManager.LayoutParams.LAST_SUB_WINDOW; import static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION; import static android.view.WindowManager.LayoutParams.TYPE_INPUT_METHOD; @@ -65,11 +68,6 @@ class WindowList extends ArrayList<WindowState> { final class WindowState implements WindowManagerPolicy.WindowState { static final String TAG = "WindowState"; - static final boolean DEBUG_VISIBILITY = WindowManagerService.DEBUG_VISIBILITY; - static final boolean SHOW_TRANSACTIONS = WindowManagerService.SHOW_TRANSACTIONS; - static final boolean SHOW_LIGHT_TRANSACTIONS = WindowManagerService.SHOW_LIGHT_TRANSACTIONS; - static final boolean SHOW_SURFACE_ALLOC = WindowManagerService.SHOW_SURFACE_ALLOC; - final WindowManagerService mService; final WindowManagerPolicy mPolicy; final Context mContext; @@ -220,6 +218,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { final Rect mContentFrame = new Rect(); final Rect mParentFrame = new Rect(); final Rect mVisibleFrame = new Rect(); + final Rect mDecorFrame = new Rect(); boolean mContentChanged; @@ -300,6 +299,10 @@ final class WindowState implements WindowManagerPolicy.WindowState { /** When true this window can be displayed on screens owther than mOwnerUid's */ private boolean mShowToOwnerOnly; + /** When true this window is at the top of the screen and should be layed out to extend under + * the status bar */ + boolean mUnderStatusBar = true; + WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a, int viewVisibility, final DisplayContent displayContent) { @@ -330,7 +333,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { mContext = mService.mContext; DeathRecipient deathRecipient = new DeathRecipient(); mSeq = seq; - mEnforceSizeCompat = (mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0; + mEnforceSizeCompat = (mAttrs.privateFlags & PRIVATE_FLAG_COMPATIBLE_WINDOW) != 0; if (WindowManagerService.localLOGV) Slog.v( TAG, "Window " + this + " client=" + c.asBinder() + " token=" + token + " (" + mAttrs.token + ")" + " params=" + a); @@ -456,17 +459,23 @@ final class WindowState implements WindowManagerPolicy.WindowState { } @Override - public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf) { + public void computeFrameLw(Rect pf, Rect df, Rect of, Rect cf, Rect vf, Rect dcf) { mHaveFrame = true; - final Rect container = mContainingFrame; - container.set(pf); + TaskStack stack = mAppToken != null ? getStack() : null; + if (stack != null && stack.hasSibling()) { + mContainingFrame.set(getStackBounds(stack)); + if (mUnderStatusBar) { + mContainingFrame.top = pf.top; + } + } else { + mContainingFrame.set(pf); + } - final Rect display = mDisplayFrame; - display.set(df); + mDisplayFrame.set(df); - final int pw = container.right - container.left; - final int ph = container.bottom - container.top; + final int pw = mContainingFrame.width(); + final int ph = mContainingFrame.height(); int w,h; if ((mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0) { @@ -513,18 +522,13 @@ final class WindowState implements WindowManagerPolicy.WindowState { mContentChanged = true; } - final Rect overscan = mOverscanFrame; - overscan.set(of); + mOverscanFrame.set(of); + mContentFrame.set(cf); + mVisibleFrame.set(vf); + mDecorFrame.set(dcf); - final Rect content = mContentFrame; - content.set(cf); - - final Rect visible = mVisibleFrame; - visible.set(vf); - - final Rect frame = mFrame; - final int fw = frame.width(); - final int fh = frame.height(); + final int fw = mFrame.width(); + final int fh = mFrame.height(); //System.out.println("In: w=" + w + " h=" + h + " container=" + // container + " x=" + mAttrs.x + " y=" + mAttrs.y); @@ -538,75 +542,69 @@ final class WindowState implements WindowManagerPolicy.WindowState { y = mAttrs.y; } - Gravity.apply(mAttrs.gravity, w, h, container, + Gravity.apply(mAttrs.gravity, w, h, mContainingFrame, (int) (x + mAttrs.horizontalMargin * pw), - (int) (y + mAttrs.verticalMargin * ph), frame); + (int) (y + mAttrs.verticalMargin * ph), mFrame); //System.out.println("Out: " + mFrame); // Now make sure the window fits in the overall display. - Gravity.applyDisplay(mAttrs.gravity, df, frame); + Gravity.applyDisplay(mAttrs.gravity, df, mFrame); // Make sure the content and visible frames are inside of the // final window frame. - if (content.left < frame.left) content.left = frame.left; - if (content.top < frame.top) content.top = frame.top; - if (content.right > frame.right) content.right = frame.right; - if (content.bottom > frame.bottom) content.bottom = frame.bottom; - if (visible.left < frame.left) visible.left = frame.left; - if (visible.top < frame.top) visible.top = frame.top; - if (visible.right > frame.right) visible.right = frame.right; - if (visible.bottom > frame.bottom) visible.bottom = frame.bottom; - - final Rect overscanInsets = mOverscanInsets; - overscanInsets.left = overscan.left > frame.left ? overscan.left-frame.left : 0; - overscanInsets.top = overscan.top > frame.top ? overscan.top-frame.top : 0; - overscanInsets.right = overscan.right < frame.right ? frame.right-overscan.right : 0; - overscanInsets.bottom = overscan.bottom < frame.bottom ? frame.bottom-overscan.bottom : 0; - - final Rect contentInsets = mContentInsets; - contentInsets.left = content.left-frame.left; - contentInsets.top = content.top-frame.top; - contentInsets.right = frame.right-content.right; - contentInsets.bottom = frame.bottom-content.bottom; - - final Rect visibleInsets = mVisibleInsets; - visibleInsets.left = visible.left-frame.left; - visibleInsets.top = visible.top-frame.top; - visibleInsets.right = frame.right-visible.right; - visibleInsets.bottom = frame.bottom-visible.bottom; - - mCompatFrame.set(frame); + mContentFrame.set(Math.max(mContentFrame.left, mFrame.left), + Math.max(mContentFrame.top, mFrame.top), + Math.min(mContentFrame.right, mFrame.right), + Math.min(mContentFrame.bottom, mFrame.bottom)); + + mVisibleFrame.set(Math.max(mVisibleFrame.left, mFrame.left), + Math.max(mVisibleFrame.top, mFrame.top), + Math.min(mVisibleFrame.right, mFrame.right), + Math.min(mVisibleFrame.bottom, mFrame.bottom)); + + mOverscanInsets.set(Math.max(mOverscanFrame.left - mFrame.left, 0), + Math.max(mOverscanFrame.top - mFrame.top, 0), + Math.max(mFrame.right - mOverscanFrame.right, 0), + Math.max(mFrame.bottom - mOverscanFrame.bottom, 0)); + + mContentInsets.set(mContentFrame.left - mFrame.left, + mContentFrame.top - mFrame.top, + mFrame.right - mContentFrame.right, + mFrame.bottom - mContentFrame.bottom); + + mVisibleInsets.set(mVisibleFrame.left - mFrame.left, + mVisibleFrame.top - mFrame.top, + mFrame.right - mVisibleFrame.right, + mFrame.bottom - mVisibleFrame.bottom); + + mCompatFrame.set(mFrame); if (mEnforceSizeCompat) { // If there is a size compatibility scale being applied to the // window, we need to apply this to its insets so that they are // reported to the app in its coordinate space. - overscanInsets.scale(mInvGlobalScale); - contentInsets.scale(mInvGlobalScale); - visibleInsets.scale(mInvGlobalScale); + mOverscanInsets.scale(mInvGlobalScale); + mContentInsets.scale(mInvGlobalScale); + mVisibleInsets.scale(mInvGlobalScale); // Also the scaled frame that we report to the app needs to be // adjusted to be in its coordinate space. mCompatFrame.scale(mInvGlobalScale); } - if (mIsWallpaper && (fw != frame.width() || fh != frame.height())) { + if (mIsWallpaper && (fw != mFrame.width() || fh != mFrame.height())) { final DisplayInfo displayInfo = mDisplayContent.getDisplayInfo(); - mService.updateWallpaperOffsetLocked(this, displayInfo.appWidth, displayInfo.appHeight, - false); + mService.updateWallpaperOffsetLocked(this, + displayInfo.logicalWidth, displayInfo.logicalHeight, false); } - if (WindowManagerService.localLOGV) { - //if ("com.google.android.youtube".equals(mAttrs.packageName) - // && mAttrs.type == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) { - Slog.v(TAG, "Resolving (mRequestedWidth=" - + mRequestedWidth + ", mRequestedheight=" - + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph - + "): frame=" + mFrame.toShortString() - + " ci=" + contentInsets.toShortString() - + " vi=" + visibleInsets.toShortString()); - //} - } + if (DEBUG_LAYOUT || WindowManagerService.localLOGV) Slog.v(TAG, + "Resolving (mRequestedWidth=" + + mRequestedWidth + ", mRequestedheight=" + + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph + + "): frame=" + mFrame.toShortString() + + " ci=" + mContentInsets.toShortString() + + " vi=" + mVisibleInsets.toShortString()); } @Override @@ -707,6 +705,28 @@ final class WindowState implements WindowManagerPolicy.WindowState { return mDisplayContent.getDisplayId(); } + TaskStack getStack() { + AppWindowToken wtoken = mAppToken == null ? mService.mFocusedApp : mAppToken; + if (wtoken != null) { + Task task = mService.mTaskIdToTask.get(wtoken.groupId); + if (task != null) { + return task.mStack; + } + } + return mDisplayContent.getHomeStack(); + } + + Rect getStackBounds() { + return getStackBounds(getStack()); + } + + private Rect getStackBounds(TaskStack stack) { + if (stack != null) { + return stack.mStackBox.mBounds; + } + return mFrame; + } + public long getInputDispatchingTimeoutNanos() { return mAppToken != null ? mAppToken.inputDispatchingTimeoutNanos @@ -901,7 +921,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { || (atoken == null && mRootToken.hidden) || (atoken != null && (atoken.hiddenRequested || atoken.hidden)) || mAttachedHidden - || mExiting || mDestroying; + || (mExiting && !isAnimatingLw()) + || mDestroying; } /** @@ -968,12 +989,6 @@ final class WindowState implements WindowManagerPolicy.WindowState { return configChanged; } - boolean isConfigDiff(int mask) { - return mConfiguration != mService.mCurConfiguration - && mConfiguration != null - && (mConfiguration.diff(mService.mCurConfiguration) & mask) != 0; - } - void removeLocked() { disposeInputChannel(); @@ -1026,7 +1041,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { Slog.i(TAG, "WIN DEATH: " + win); if (win != null) { mService.removeWindowLocked(mSession, win); - } else if (WindowState.this.mHasSurface) { + } else if (mHasSurface) { Slog.e(TAG, "!!! LEAK !!! Window removed but surface still valid."); mService.removeWindowLocked(mSession, WindowState.this); } @@ -1129,6 +1144,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { // we allow the display to be enabled now. mService.enableScreenIfNeededLocked(); if (mService.mCurrentFocus == this) { + if (WindowManagerService.DEBUG_FOCUS_LIGHT) Slog.i(TAG, + "WindowState.hideLw: setting mFocusMayChange true"); mService.mFocusMayChange = true; } } @@ -1138,7 +1155,7 @@ final class WindowState implements WindowManagerPolicy.WindowState { return true; } - public boolean setAppOpVisibilityLw(boolean state) { + public void setAppOpVisibilityLw(boolean state) { if (mAppOpVisibility != state) { mAppOpVisibility = state; if (state) { @@ -1148,13 +1165,11 @@ final class WindowState implements WindowManagerPolicy.WindowState { // ops modifies they should only be hidden by policy due to the // lock screen, and the user won't be changing this if locked. // Plus it will quickly be fixed the next time we do a layout. - showLw(true, false); + showLw(true, true); } else { - hideLw(true, false); + hideLw(true, true); } - return true; } - return false; } @Override @@ -1391,6 +1406,8 @@ final class WindowState implements WindowManagerPolicy.WindowState { pw.print(prefix); pw.print(" content="); mContentFrame.printShortString(pw); pw.print(" visible="); mVisibleFrame.printShortString(pw); pw.println(); + pw.print(prefix); pw.print(" decor="); mDecorFrame.printShortString(pw); + pw.println(); pw.print(prefix); pw.print("Cur insets: overscan="); mOverscanInsets.printShortString(pw); pw.print(" content="); mContentInsets.printShortString(pw); diff --git a/services/java/com/android/server/wm/WindowStateAnimator.java b/services/java/com/android/server/wm/WindowStateAnimator.java index c07174baf4c3..e2fae89eec0b 100644 --- a/services/java/com/android/server/wm/WindowStateAnimator.java +++ b/services/java/com/android/server/wm/WindowStateAnimator.java @@ -3,6 +3,16 @@ package com.android.server.wm; import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_STARTING; +import static com.android.server.wm.WindowManagerService.DEBUG_ANIM; +import static com.android.server.wm.WindowManagerService.DEBUG_LAYERS; +import static com.android.server.wm.WindowManagerService.DEBUG_ORIENTATION; +import static com.android.server.wm.WindowManagerService.DEBUG_STARTING_WINDOW; +import static com.android.server.wm.WindowManagerService.DEBUG_SURFACE_TRACE; +import static com.android.server.wm.WindowManagerService.SHOW_TRANSACTIONS; +import static com.android.server.wm.WindowManagerService.DEBUG_VISIBILITY; +import static com.android.server.wm.WindowManagerService.SHOW_LIGHT_TRANSACTIONS; +import static com.android.server.wm.WindowManagerService.SHOW_SURFACE_ALLOC; +import static com.android.server.wm.WindowManagerService.localLOGV; import static com.android.server.wm.WindowManagerService.LayoutFields.SET_ORIENTATION_CHANGE_COMPLETE; import static com.android.server.wm.WindowManagerService.LayoutFields.SET_TURN_ON_SCREEN; @@ -19,7 +29,7 @@ import android.util.Slog; import android.view.Display; import android.view.DisplayInfo; import android.view.MagnificationSpec; -import android.view.Surface; +import android.view.Surface.OutOfResourcesException; import android.view.SurfaceControl; import android.view.SurfaceSession; import android.view.WindowManager; @@ -35,30 +45,12 @@ import java.io.PrintWriter; import java.util.ArrayList; class WinAnimatorList extends ArrayList<WindowStateAnimator> { - public WinAnimatorList() { - super(); - } - - public WinAnimatorList(WinAnimatorList other) { - super(other); - } } /** * Keep track of animations and surface operations for a single WindowState. **/ class WindowStateAnimator { - static final boolean DEBUG_VISIBILITY = WindowManagerService.DEBUG_VISIBILITY; - static final boolean DEBUG_ANIM = WindowManagerService.DEBUG_ANIM; - static final boolean DEBUG_LAYERS = WindowManagerService.DEBUG_LAYERS; - static final boolean DEBUG_STARTING_WINDOW = WindowManagerService.DEBUG_STARTING_WINDOW; - static final boolean SHOW_TRANSACTIONS = WindowManagerService.SHOW_TRANSACTIONS; - static final boolean SHOW_LIGHT_TRANSACTIONS = WindowManagerService.SHOW_LIGHT_TRANSACTIONS; - static final boolean SHOW_SURFACE_ALLOC = WindowManagerService.SHOW_SURFACE_ALLOC; - static final boolean localLOGV = WindowManagerService.localLOGV; - static final boolean DEBUG_ORIENTATION = WindowManagerService.DEBUG_ORIENTATION; - static final boolean DEBUG_SURFACE_TRACE = WindowManagerService.DEBUG_SURFACE_TRACE; - static final String TAG = "WindowStateAnimator"; // Unchanging local convenience fields. @@ -340,7 +332,7 @@ class WindowStateAnimator { mHasTransformation = false; mHasLocalTransformation = false; if (mWin.mPolicyVisibility != mWin.mPolicyVisibilityAfterAnim) { - if (WindowState.DEBUG_VISIBILITY) { + if (DEBUG_VISIBILITY) { Slog.v(TAG, "Policy visibility changing after anim in " + this + ": " + mWin.mPolicyVisibilityAfterAnim); } @@ -348,6 +340,8 @@ class WindowStateAnimator { mWin.mDisplayContent.layoutNeeded = true; if (!mWin.mPolicyVisibility) { if (mService.mCurrentFocus == mWin) { + if (WindowManagerService.DEBUG_FOCUS_LIGHT) Slog.i(TAG, + "setAnimationLocked: setting mFocusMayChange true"); mService.mFocusMayChange = true; } // Window is no longer visible -- make sure if we were waiting @@ -407,7 +401,7 @@ class WindowStateAnimator { if (mSurfaceControl != null) { mService.mDestroySurface.add(mWin); mWin.mDestroying = true; - if (WindowState.SHOW_TRANSACTIONS) WindowManagerService.logSurface( + if (SHOW_TRANSACTIONS) WindowManagerService.logSurface( mWin, "HIDE (finishExit)", null); hide(); } @@ -489,7 +483,7 @@ class WindowStateAnimator { private final Rect mWindowCrop = new Rect(); private boolean mShown = false; private int mLayerStack; - private String mName; + private final String mName; public SurfaceTrace(SurfaceSession s, String name, int w, int h, int format, int flags) @@ -503,22 +497,22 @@ class WindowStateAnimator { @Override public void setAlpha(float alpha) { - super.setAlpha(alpha); - if (alpha != mSurfaceTraceAlpha) { - mSurfaceTraceAlpha = alpha; - Slog.v(SURFACE_TAG, "setAlpha: " + this + ". Called by " + if (mSurfaceTraceAlpha != alpha) { + Slog.v(SURFACE_TAG, "setAlpha(" + alpha + "): OLD:" + this + ". Called by " + Debug.getCallers(3)); + mSurfaceTraceAlpha = alpha; } + super.setAlpha(alpha); } @Override public void setLayer(int zorder) { - super.setLayer(zorder); if (zorder != mLayer) { - mLayer = zorder; - Slog.v(SURFACE_TAG, "setLayer: " + this + ". Called by " + Slog.v(SURFACE_TAG, "setLayer(" + zorder + "): OLD:" + this + ". Called by " + Debug.getCallers(3)); + mLayer = zorder; } + super.setLayer(zorder); sSurfaces.remove(this); int i; @@ -533,69 +527,68 @@ class WindowStateAnimator { @Override public void setPosition(float x, float y) { - super.setPosition(x, y); if (x != mPosition.x || y != mPosition.y) { + Slog.v(SURFACE_TAG, "setPosition(" + x + "," + y + "): OLD:" + this + + ". Called by " + Debug.getCallers(3)); mPosition.set(x, y); - Slog.v(SURFACE_TAG, "setPosition: " + this + ". Called by " - + Debug.getCallers(3)); } + super.setPosition(x, y); } @Override public void setSize(int w, int h) { - super.setSize(w, h); if (w != mSize.x || h != mSize.y) { - mSize.set(w, h); - Slog.v(SURFACE_TAG, "setSize: " + this + ". Called by " + Slog.v(SURFACE_TAG, "setSize(" + w + "," + h + "): OLD:" + this + ". Called by " + Debug.getCallers(3)); + mSize.set(w, h); } + super.setSize(w, h); } @Override public void setWindowCrop(Rect crop) { - super.setWindowCrop(crop); if (crop != null) { if (!crop.equals(mWindowCrop)) { + Slog.v(SURFACE_TAG, "setWindowCrop(" + crop.toShortString() + "): OLD:" + this + + ". Called by " + Debug.getCallers(3)); mWindowCrop.set(crop); - Slog.v(SURFACE_TAG, "setWindowCrop: " + this + ". Called by " - + Debug.getCallers(3)); } } + super.setWindowCrop(crop); } @Override public void setLayerStack(int layerStack) { - super.setLayerStack(layerStack); if (layerStack != mLayerStack) { + Slog.v(SURFACE_TAG, "setLayerStack(" + layerStack + "): OLD:" + this + + ". Called by " + Debug.getCallers(3)); mLayerStack = layerStack; - Slog.v(SURFACE_TAG, "setLayerStack: " + this + ". Called by " + Debug.getCallers(3)); } + super.setLayerStack(layerStack); } @Override public void hide() { - super.hide(); if (mShown) { + Slog.v(SURFACE_TAG, "hide: OLD:" + this + ". Called by " + Debug.getCallers(3)); mShown = false; - Slog.v(SURFACE_TAG, "hide: " + this + ". Called by " - + Debug.getCallers(3)); } + super.hide(); } + @Override public void show() { - super.show(); if (!mShown) { + Slog.v(SURFACE_TAG, "show: OLD:" + this + ". Called by " + Debug.getCallers(3)); mShown = true; - Slog.v(SURFACE_TAG, "show: " + this + ". Called by " - + Debug.getCallers(3)); } + super.show(); } @Override public void destroy() { super.destroy(); - Slog.v(SURFACE_TAG, "destroy: " + this + ". Called by " - + Debug.getCallers(3)); + Slog.v(SURFACE_TAG, "destroy: " + this + ". Called by " + Debug.getCallers(3)); sSurfaces.remove(this); } @@ -648,7 +641,7 @@ class WindowStateAnimator { if ((attrs.flags&WindowManager.LayoutParams.FLAG_SECURE) != 0) { flags |= SurfaceControl.SECURE; } - if (WindowState.DEBUG_VISIBILITY) Slog.v( + if (DEBUG_VISIBILITY) Slog.v( TAG, "Creating surface in session " + mSession.mSurfaceSession + " window " + this + " w=" + mWin.mCompatFrame.width() @@ -704,7 +697,7 @@ class WindowStateAnimator { + attrs.format + " flags=0x" + Integer.toHexString(flags) + " / " + this); - } catch (SurfaceControl.OutOfResourcesException e) { + } catch (OutOfResourcesException e) { mWin.mHasSurface = false; Slog.w(TAG, "OutOfResourcesException creating surface"); mService.reclaimSomeSurfaceMemoryLocked(this, "create", true); @@ -1107,12 +1100,14 @@ class WindowStateAnimator { } else { applyDecorRect(mService.mScreenRect); } - } else if (w.mAttrs.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND) { - // The universe background isn't cropped. + } else if (w.mAttrs.type == WindowManager.LayoutParams.TYPE_UNIVERSE_BACKGROUND + || w.mDecorFrame.isEmpty()) { + // The universe background isn't cropped, nor windows without policy decor. w.mSystemDecorRect.set(0, 0, w.mCompatFrame.width(), w.mCompatFrame.height()); } else { - applyDecorRect(mService.mSystemDecorRect); + // Crop to the system decor specified by policy. + applyDecorRect(w.mDecorFrame); } if (!w.mSystemDecorRect.equals(w.mLastSystemDecorRect)) { @@ -1185,7 +1180,7 @@ class WindowStateAnimator { mAnimator.setPendingLayoutChanges(displayId, WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER); if ((w.mAttrs.flags & LayoutParams.FLAG_DIM_BEHIND) != 0) { - mService.startDimmingLocked(this, w.mExiting ? 0 : w.mAttrs.dimAmount); + w.getStack().startDimmingIfNeeded(this); } } catch (RuntimeException e) { // If something goes wrong with the surface (such @@ -1282,6 +1277,11 @@ class WindowStateAnimator { if (mIsWallpaper) { mService.dispatchWallpaperVisibility(w, true); } + // This draw means the difference between unique content and mirroring. + // Run another pass through performLayout to set mHasContent in the + // LogicalDisplay. + mAnimator.setPendingLayoutChanges(w.getDisplayId(), + WindowManagerPolicy.FINISH_LAYOUT_REDO_ANIM); } else { w.mOrientationChanging = false; } diff --git a/services/java/com/android/server/wm/WindowToken.java b/services/java/com/android/server/wm/WindowToken.java index bd0ace89e49d..22671234c538 100644 --- a/services/java/com/android/server/wm/WindowToken.java +++ b/services/java/com/android/server/wm/WindowToken.java @@ -19,7 +19,6 @@ package com.android.server.wm; import android.os.IBinder; import java.io.PrintWriter; -import java.util.ArrayList; /** * Container of a set of related windows in the window manager. Often this |