summaryrefslogtreecommitdiff
path: root/services/java
diff options
context:
space:
mode:
Diffstat (limited to 'services/java')
-rw-r--r--services/java/Android.mk2
-rw-r--r--services/java/com/android/server/AlarmManagerService.java1017
-rw-r--r--services/java/com/android/server/AppOpsService.java233
-rw-r--r--services/java/com/android/server/AppWidgetService.java8
-rw-r--r--services/java/com/android/server/AppWidgetServiceImpl.java42
-rw-r--r--services/java/com/android/server/AssetAtlasService.java735
-rw-r--r--services/java/com/android/server/BackupManagerService.java82
-rw-r--r--services/java/com/android/server/BatteryService.java282
-rw-r--r--services/java/com/android/server/BluetoothManagerService.java50
-rw-r--r--services/java/com/android/server/BootReceiver.java28
-rw-r--r--services/java/com/android/server/ClipboardService.java4
-rw-r--r--services/java/com/android/server/CommonTimeManagementService.java11
-rw-r--r--services/java/com/android/server/ConnectivityService.java1231
-rw-r--r--services/java/com/android/server/ConsumerIrService.java135
-rw-r--r--services/java/com/android/server/CountryDetectorService.java18
-rw-r--r--services/java/com/android/server/DevicePolicyManagerService.java385
-rw-r--r--services/java/com/android/server/DropBoxManagerService.java10
-rw-r--r--services/java/com/android/server/FgThread.java65
-rw-r--r--services/java/com/android/server/IdleMaintenanceService.java69
-rw-r--r--services/java/com/android/server/InputMethodManagerService.java77
-rw-r--r--services/java/com/android/server/IntentResolver.java212
-rw-r--r--services/java/com/android/server/IntentResolverOld.java638
-rw-r--r--services/java/com/android/server/IoThread.java62
-rw-r--r--services/java/com/android/server/LocationManagerService.java215
-rw-r--r--services/java/com/android/server/LockSettingsService.java128
-rw-r--r--services/java/com/android/server/MountService.java117
-rw-r--r--services/java/com/android/server/NativeDaemonConnector.java21
-rw-r--r--services/java/com/android/server/NetworkManagementService.java320
-rw-r--r--services/java/com/android/server/NetworkTimeUpdateService.java9
-rw-r--r--services/java/com/android/server/NotificationManagerService.java569
-rw-r--r--services/java/com/android/server/NsdService.java8
-rw-r--r--services/java/com/android/server/PreferredComponent.java45
-rw-r--r--services/java/com/android/server/ProcessMap.java56
-rw-r--r--services/java/com/android/server/StatusBarManagerService.java9
-rw-r--r--services/java/com/android/server/SystemServer.java610
-rw-r--r--services/java/com/android/server/TelephonyRegistry.java2
-rw-r--r--services/java/com/android/server/TextServicesManagerService.java4
-rw-r--r--services/java/com/android/server/TwilightService.java2
-rw-r--r--services/java/com/android/server/UiThread.java71
-rw-r--r--services/java/com/android/server/VibratorService.java10
-rw-r--r--services/java/com/android/server/WallpaperManagerService.java25
-rw-r--r--services/java/com/android/server/Watchdog.java374
-rw-r--r--services/java/com/android/server/WiredAccessoryManager.java5
-rw-r--r--services/java/com/android/server/accessibility/AccessibilityManagerService.java13
-rw-r--r--services/java/com/android/server/accessibility/ScreenMagnifier.java1
-rw-r--r--services/java/com/android/server/accessibility/TouchExplorer.java794
-rw-r--r--services/java/com/android/server/accounts/AccountManagerService.java144
-rw-r--r--services/java/com/android/server/am/ActiveServices.java1178
-rw-r--r--services/java/com/android/server/am/ActivityManagerService.java6060
-rw-r--r--services/java/com/android/server/am/ActivityRecord.java373
-rw-r--r--services/java/com/android/server/am/ActivityResult.java2
-rw-r--r--services/java/com/android/server/am/ActivityStack.java4831
-rw-r--r--services/java/com/android/server/am/ActivityStackSupervisor.java2632
-rw-r--r--services/java/com/android/server/am/AppBindRecord.java2
-rw-r--r--services/java/com/android/server/am/AppErrorDialog.java8
-rw-r--r--services/java/com/android/server/am/AppErrorResult.java3
-rw-r--r--services/java/com/android/server/am/AppNotRespondingDialog.java9
-rw-r--r--services/java/com/android/server/am/AppWaitingForDebuggerDialog.java2
-rw-r--r--services/java/com/android/server/am/BackupRecord.java2
-rw-r--r--services/java/com/android/server/am/BatteryStatsService.java46
-rw-r--r--services/java/com/android/server/am/BroadcastFilter.java2
-rw-r--r--services/java/com/android/server/am/BroadcastQueue.java168
-rw-r--r--services/java/com/android/server/am/BroadcastRecord.java10
-rw-r--r--services/java/com/android/server/am/CompatModeDialog.java2
-rw-r--r--services/java/com/android/server/am/CompatModePackages.java33
-rw-r--r--services/java/com/android/server/am/ConnectionRecord.java11
-rw-r--r--services/java/com/android/server/am/ContentProviderConnection.java2
-rw-r--r--services/java/com/android/server/am/ContentProviderRecord.java2
-rw-r--r--services/java/com/android/server/am/CoreSettingsObserver.java2
-rw-r--r--services/java/com/android/server/am/DeviceMonitor.java234
-rw-r--r--services/java/com/android/server/am/EventLogTags.logtags5
-rw-r--r--services/java/com/android/server/am/FactoryErrorDialog.java2
-rw-r--r--services/java/com/android/server/am/IntentBindRecord.java33
-rw-r--r--services/java/com/android/server/am/LaunchWarningWindow.java2
-rw-r--r--services/java/com/android/server/am/NativeCrashListener.java9
-rw-r--r--services/java/com/android/server/am/PendingIntentRecord.java4
-rw-r--r--services/java/com/android/server/am/PendingThumbnailsRecord.java6
-rw-r--r--services/java/com/android/server/am/ProcessList.java352
-rw-r--r--services/java/com/android/server/am/ProcessMemInfo.java37
-rw-r--r--services/java/com/android/server/am/ProcessRecord.java328
-rw-r--r--services/java/com/android/server/am/ProcessStatsService.java899
-rw-r--r--services/java/com/android/server/am/ProviderMap.java70
-rw-r--r--services/java/com/android/server/am/ReceiverList.java4
-rw-r--r--services/java/com/android/server/am/ServiceRecord.java102
-rw-r--r--services/java/com/android/server/am/StrictModeViolationDialog.java7
-rw-r--r--services/java/com/android/server/am/TaskRecord.java369
-rw-r--r--services/java/com/android/server/am/ThumbnailHolder.java5
-rw-r--r--services/java/com/android/server/am/TransferPipe.java242
-rw-r--r--services/java/com/android/server/am/UriPermission.java328
-rw-r--r--services/java/com/android/server/am/UriPermissionOwner.java34
-rw-r--r--services/java/com/android/server/am/UsageStatsService.java263
-rw-r--r--services/java/com/android/server/am/UserStartedState.java2
-rw-r--r--services/java/com/android/server/connectivity/DataConnectionStats.java154
-rw-r--r--services/java/com/android/server/connectivity/PacManager.java381
-rw-r--r--services/java/com/android/server/connectivity/Tethering.java26
-rw-r--r--services/java/com/android/server/connectivity/Vpn.java382
-rw-r--r--services/java/com/android/server/content/ContentService.java156
-rw-r--r--services/java/com/android/server/content/SyncManager.java376
-rw-r--r--services/java/com/android/server/content/SyncOperation.java150
-rw-r--r--services/java/com/android/server/content/SyncQueue.java41
-rw-r--r--services/java/com/android/server/content/SyncStorageEngine.java791
-rw-r--r--services/java/com/android/server/display/DisplayDeviceInfo.java76
-rw-r--r--services/java/com/android/server/display/DisplayManagerService.java366
-rw-r--r--services/java/com/android/server/display/LocalDisplayAdapter.java1
-rw-r--r--services/java/com/android/server/display/LogicalDisplay.java30
-rw-r--r--services/java/com/android/server/display/OverlayDisplayAdapter.java46
-rw-r--r--services/java/com/android/server/display/OverlayDisplayWindow.java14
-rw-r--r--services/java/com/android/server/display/PersistentDataStore.java6
-rw-r--r--services/java/com/android/server/display/VirtualDisplayAdapter.java177
-rw-r--r--services/java/com/android/server/display/WifiDisplayAdapter.java103
-rw-r--r--services/java/com/android/server/display/WifiDisplayController.java142
-rw-r--r--services/java/com/android/server/dreams/DreamManagerService.java10
-rw-r--r--services/java/com/android/server/firewall/AndFilter.java10
-rw-r--r--services/java/com/android/server/firewall/CategoryFilter.java6
-rw-r--r--services/java/com/android/server/firewall/Filter.java17
-rw-r--r--services/java/com/android/server/firewall/IntentFirewall.java267
-rw-r--r--services/java/com/android/server/firewall/NotFilter.java10
-rw-r--r--services/java/com/android/server/firewall/OrFilter.java10
-rw-r--r--services/java/com/android/server/firewall/PortFilter.java6
-rw-r--r--services/java/com/android/server/firewall/SenderFilter.java56
-rw-r--r--services/java/com/android/server/firewall/SenderPermissionFilter.java8
-rw-r--r--services/java/com/android/server/firewall/StringFilter.java66
-rw-r--r--services/java/com/android/server/input/InputManagerService.java9
-rw-r--r--services/java/com/android/server/input/InputWindowHandle.java1
-rw-r--r--services/java/com/android/server/location/FlpHardwareProvider.java427
-rw-r--r--services/java/com/android/server/location/FusedLocationHardwareSecure.java119
-rw-r--r--services/java/com/android/server/location/FusedProxy.java128
-rw-r--r--services/java/com/android/server/location/GeofenceProxy.java89
-rw-r--r--services/java/com/android/server/location/GpsLocationProvider.java143
-rw-r--r--services/java/com/android/server/net/BaseNetworkObserver.java57
-rw-r--r--services/java/com/android/server/net/LockdownVpnTracker.java31
-rw-r--r--services/java/com/android/server/net/NetworkPolicyManagerService.java7
-rw-r--r--services/java/com/android/server/net/NetworkStatsRecorder.java9
-rw-r--r--services/java/com/android/server/net/NetworkStatsService.java36
-rw-r--r--services/java/com/android/server/pm/GrantedPermissions.java1
-rw-r--r--services/java/com/android/server/pm/KeySetManager.java586
-rw-r--r--services/java/com/android/server/pm/PackageKeySetData.java125
-rwxr-xr-xservices/java/com/android/server/pm/PackageManagerService.java1150
-rw-r--r--services/java/com/android/server/pm/PackageSetting.java6
-rw-r--r--services/java/com/android/server/pm/PackageSettingBase.java18
-rw-r--r--services/java/com/android/server/pm/PreferredActivity.java12
-rw-r--r--services/java/com/android/server/pm/PreferredIntentResolver.java2
-rw-r--r--services/java/com/android/server/pm/Settings.java404
-rw-r--r--services/java/com/android/server/pm/SharedUserSetting.java23
-rw-r--r--services/java/com/android/server/pm/UserManagerService.java348
-rw-r--r--services/java/com/android/server/power/DisplayPowerController.java100
-rw-r--r--services/java/com/android/server/power/ElectronBeam.java17
-rw-r--r--services/java/com/android/server/power/Notifier.java25
-rw-r--r--services/java/com/android/server/power/PowerManagerService.java158
-rw-r--r--services/java/com/android/server/power/ShutdownThread.java7
-rw-r--r--services/java/com/android/server/power/WirelessChargerDetector.java119
-rw-r--r--services/java/com/android/server/print/PrintManagerService.java651
-rw-r--r--services/java/com/android/server/print/RemotePrintService.java806
-rw-r--r--services/java/com/android/server/print/RemotePrintSpooler.java634
-rw-r--r--services/java/com/android/server/print/UserState.java1620
-rw-r--r--services/java/com/android/server/updates/IntentFirewallInstallReceiver.java5
-rw-r--r--services/java/com/android/server/usb/UsbDebuggingManager.java10
-rw-r--r--services/java/com/android/server/usb/UsbDeviceManager.java12
-rw-r--r--services/java/com/android/server/wifi/WifiController.java14
-rw-r--r--services/java/com/android/server/wifi/WifiService.java345
-rw-r--r--services/java/com/android/server/wm/AppTransition.java2
-rw-r--r--services/java/com/android/server/wm/AppWindowAnimator.java1
-rw-r--r--services/java/com/android/server/wm/AppWindowToken.java4
-rw-r--r--services/java/com/android/server/wm/BlackFrame.java24
-rw-r--r--services/java/com/android/server/wm/DimLayer.java71
-rw-r--r--services/java/com/android/server/wm/DisplayContent.java387
-rw-r--r--services/java/com/android/server/wm/DisplayMagnifier.java6
-rw-r--r--services/java/com/android/server/wm/DragState.java1
-rw-r--r--services/java/com/android/server/wm/FakeWindowImpl.java5
-rw-r--r--services/java/com/android/server/wm/FocusedStackFrame.java142
-rw-r--r--services/java/com/android/server/wm/InputMonitor.java59
-rw-r--r--services/java/com/android/server/wm/PointerEventDispatcher.java90
-rw-r--r--services/java/com/android/server/wm/ScreenRotationAnimation.java84
-rw-r--r--services/java/com/android/server/wm/Session.java2
-rw-r--r--services/java/com/android/server/wm/StackBox.java418
-rw-r--r--services/java/com/android/server/wm/StackTapPointerEventListener.java87
-rw-r--r--services/java/com/android/server/wm/StartingData.java4
-rw-r--r--services/java/com/android/server/wm/StrictModeFlash.java3
-rw-r--r--services/java/com/android/server/wm/Task.java54
-rw-r--r--services/java/com/android/server/wm/TaskGroup.java31
-rw-r--r--services/java/com/android/server/wm/TaskStack.java310
-rw-r--r--services/java/com/android/server/wm/Watermark.java8
-rw-r--r--services/java/com/android/server/wm/WindowAnimator.java308
-rw-r--r--services/java/com/android/server/wm/WindowManagerService.java2256
-rw-r--r--services/java/com/android/server/wm/WindowState.java189
-rw-r--r--services/java/com/android/server/wm/WindowStateAnimator.java108
-rw-r--r--services/java/com/android/server/wm/WindowToken.java1
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